setIntervalとsetTimeoutの罠。
setIntervalとsetTimeoutで関数を渡す時に起こる素敵な罠と対処法についてのメモです。
thisの行方。
setInterval( 'func', time )
アニメーションや動作のウェイトなどで活躍するsetIntervalとsetTimeoutが、言うまでもなく繰り返す事が前提なため、いか軽く組めるかが重要になる関数ですが、そうなると必ず通る罠がthis
の行方です。setIntervalを例に、関数を組んでいきます。
var constructor = function(){
this.loop();
};
constructor.prototype = {
loop : function(){
setInterval( this.func, 1000 );
}
,dateNow : function(){
return new Date()
}
,func : function(){
console.log( this.dateNow() );
}
};
new constructor;
同じ事を繰り返すだけなのでメモリ領域は節約したい所です。インスタンス、もしくはプロトタイプで定義してしまいたいですが、これだとfunc
内のthisが参照出来ずに未定義が発生します。なぜか。実はこの場合のthis
はグローバル、つまりwindowを参照しているんです。なぜそうなるのか、全く意味不明な訳ですが、答えはこの二つの関数の書き方に明らかな答えがありましたね。
この子たちevalしてた。
setInterval( 'func', time )
そう、なぜかsetIntervalにはリテラルで関数が指定出来ます。この形は明らかにあれですね。
eval( 'func' )
eval
さんです。eval
は指定された文字列をjavascriptとしてグローバル領域で評価します。つまり、グローバルで評価されたthis
はwindowを参照してしまう訳です。
回避するためには。
いくつか回避方法はありますが、やはり引数で渡すのが一番無難かもしれません。
引数で渡す。
setInterval( 'func', time, this )
この方法でfunc
側に引数としてthis
を渡す事が出来ます。
var constructor = function(){
this.loop();
};
constructor.prototype = {
loop : function(){
setInterval( this.func, 1000, this );
}
,dateNow : function(){
return new Date()
}
,func : function(that){
console.log( that.dateNow() );
}
};
new constructor;
と書く事で解決出来ます。そして少し目的と外れてしまいますが、もう一つ可能な方法があります。
関数内のthis
をオブジェクトとして渡してしまう。
var constructor = function(){
this.loop();
};
constructor.prototype = {
loop : function(){
var self = this;
setInterval(
function(){
console.log( self.dateNow() );
}, 1000 );
}
,dateNow : function(){
return new Date()
}
};
new constructor;
直接動作を埋め込める場合は、this
自体をオブジェクトとして変数に格納して渡せば、ちゃんと同スコープ内で参照してくれます。未だに少し違和感を覚えますが…eval
仕様です。
なんと言うか、javascriptにもwait
関数欲しいところです。