読者です 読者をやめる 読者になる 読者になる

ECMA初心者のためのスコープ講座+コールバック

スコープ

JSやASなどのECMAスクリプトの言語として非常に強い点として匿名関数があります
ただしスコープが直感的でないので注意が必要です
ActionScript2というのをカテゴリーに含めていますが、AS2以降ではかなり体系化したクラスを作成することが可能なのでそれほど分かりにくくなることは無いかもしれません。

基本的にJavaScriptのコードを例に説明していきます

Hoge = function(){}
Hoge.prototype = {
	_value:undefined,
	test:function( value ){
		this._value = value;
		//TEST1
		alert( this._value );//【1】OK
		//TEST2
		var testMethod1 = function(){
			alert( this._value );//【2】NG
		}
		testMethod1();
		//TEST3
		var obj = new Object();
		obj.testMethod2 = function(){
			alert( this._value );//【3】NG
		}
		obj.testMethod2();
	}
}
var hoge = new Hoge();
hoge.test( 'ああああ' );

たとえば上記では1の箇所では値がとれますが、2でも3でもundefinedが返ってきてしまい正しい値がとれません
おそらく慣れていない人がみたらどうしてとれないのか分かりにくいのではないのでしょうか
2ではthisの表すところがtestMethod1 、3ではobjをさします。


では次に以下のような場合を見ます

Hoge = function(){}
Hoge.prototype = {
	_value:undefined,
	test:function( value ){
		this._value = value;
		var ref = this;// LOOK HERE!!
		//TEST1
		alert( ref._value );//【1】OK
		//TEST2
		var testMethod1 = function(){
			alert( ref._value );//【2】OK
		}
		testMethod1();
		//TEST3
		var obj = new Object();
		obj.testMethod2 = function(){
			alert( ref._value );//【3】OK
		}
		obj.testMethod2();
	}
}
var hoge = new Hoge();
hoge.test( 'ああああ' );

前との違いは直前でrefという変数にthisを代入してそれぞれのalertの中のthisをrefに変更しているところです。
この場合には全ての箇所で値をとることができます。
これはスコープチェーンなどと言われるもので、値が見つからない場合に近場で同じ名前の変数を探しにいくようなものとでも思ってください。


たとえば2の箇所ではthis.refがみつからないので上で定義したrefを見に行きます。
上で定義したrefはthisを示すので結果的に値を見に行くことが可能になるわけです。
ただしこれが働くのはthisを省略した場合で、明示的にthis.ref._valueとした場合にはthis.refはありませんよということで値をとることはできません。


こういった表記はAbstractクラスのように未定義のメソッドを含むクラスを作っていて外から上書きする場合によく使います。例えば通信を行うクラスで通信の完了を通知したり、イベント監視クラスを作ってイベントの発生を受け取るのに良く使います。
分かりやすいところではJavaScript

window.onload = function() {
}

等の表記がこれにあたります。

コールバック

コールバックというのは何かのイベントが発生する時に呼ばれる関数を後から設定して、そのイベントが呼ばれたときにコールされるものだとでも思っておいてください。


大きく分けて2つの方法があります
一つ目は関数そのものの参照を渡す方法です
代表的な関数としてsetTimeoutなどがあります

Hoge = function(){}
Hoge.prototype = {
	_value:undefined,
	test:function( value ){
		this._value = value;
		setTimeout( this.callBackMethod , 1000 );
	},
	callBackMethod:function(){
		alert( this._value );//NG
	}
}
var hoge = new Hoge();
hoge.test( 'ああああ' );

ただしこの方法は(上で書いた)スコープの理由によってthis._valueが取れません
これはかなり直感的に分かりにくいです
仕組み的には

var hoge = function(){}//関数の参照を代入
hoge();//コール
//たとえばこういうこと
foo = hoge;//関数の参照をさらにfooに代入
foo();//コール

といったような仕組みです



もうひとつは

path['methodname']();

という方法です。この場合の良いてんは呼ばれたメソッドの中のthis参照はpathを指すということです

Hoge = function(){}
Hoge.prototype = {
	_value:undefined,
	test:function( value ){
		this._value = value;
		var fuge = new Fuge();
		fuge.setCallBack( this , '__callBackMethod' );//【1】
		fuge.callCallBack();
	},
	__callBackMethod:function(){
		alert( this._value );//OK
	}
}

Fuge = function(){}
Fuge.prototype = {
	_path:undefined,
	_method:undefined,
	setCallBack:function( path , method ){
		this._path   = path;
		this._method = method;
	},
	callCallBack:function(){
		this._path[ this._method ]();//【2】
	}
}

var hoge = new Hoge();
hoge.test( 'ああああ' );

JavaScriptでこの形式を使っている標準関数がぱっと思いつかなかったのでFugeというクラスを作っています
ここでは1でパスとそのパスに含まれるメソッド名を指定して2でコールしています
この場合には1でのthisという参照のまま実行されるので感覚的な参照になっているのではないでしょうか
クラス単位で分割して開発するような場合こういった書き方は、コールする際に明示的に参照先を固定化できるので非常に有用だと思います。


prototype.jsのbindのように簡単にメソッド内のthisの参照を固定する関数もあるのでそういったものを使うのもよいでしょう。ただしコールするタイミングでの記述なので忘れた場合には参照がくずれます。
開発する人数や、物によっては注意が必要かもしれません。