- 2006-07-07 (金)
- ActionScript
torus solutions!さんのエントリShibuya.js: ActionScript でクロージャ、継続渡しを読んで、以前クロージャでハマった時のことを思い出した。以下、動作環境 ActionScript2.0 + Flash8 前提でのお話。
まずはクロージャ基本形
function setFunc (){
var lex = '1 dayo';
return function (){ return lex };
}
var obj = {};
obj.func = setFunc();
trace( lex ); // undefined
trace( obj.func() ); // 1 dayo
これをループ中におこなうと・・・
// set closure var arr = []; for (var i=0;i<3;i++){ var obj = {}; var lex = i + ' dayo'; obj.func = function (){ return lex }; arr.push(obj); } // and see what happens... for (var i=0; i<arr.length; i++){ trace( arr[i].func() ); }
わざわざ setFunc() 関数を用意するの面倒なので、ループ iteration の中で直接無名関数を作っちゃおうとしているのがポイント。で、これの実行結果は:
2 dayo
2 dayo
2 dayo
これはまずい
ループ中に定義した3つのオブジェクトすべての func メソッドが、ループ最後に定義したものにすべて置き換わってしまってます。なんてこった。
正しいかたち
正常に動作させるには、下記コードのように、クロージャを返す関数をループの外で定義しておいた上で、ループ中からはきちんと引数渡しの形で同関数をコールするように形式に変える必要があります:
function setFunc (v){
return function (){ return v };
}
// set closure
var arr = [];
for (var i=0;i<3;i++){
var obj = {};
var lex = i + ' dayo';
obj.func = setFunc(lex);
arr.push(obj);
}
// and see what happens...
for (var i=0; i<arr.length; i++){
trace( arr[i].func() );
}
実行結果:
0 dayo
1 dayo
2 dayo
ふう。これで問題なしと。閉じ込めたい変数がたくさんある場合、いちいち引数渡しする事を面倒くさがったのが裏目に出ちゃいました。
追加検証1 - Perl で同じ事してみた場合
ActionScriptで挙動がおかしかったコードと同じものを Perl でも試してみました:
#!/usr/bin/perl
use strict; my @arr;
for my $i (1 .. 3){
my $obj = {};
my $lex = $i . ' desu';
$obj->{func} = sub { return $lex };
push(@arr,$obj);
}
# see what happens
for my $obj (@arr){
print $obj->{func}() . "\n";
}
実行結果:
1 dayo
2 dayo
3 dayo
期待通りの結果。こうしてPerlでうまく動くんだったら、やっぱりActionScriptがイケてないのでは?・・・なんて、なんでもかんでもPerl基準で物事を捉えてしまう症候群。
追加検証2 - Javascript で同じ事してみた場合
じゃあ Javascript だとどうよ?
<script type="text/javascript">
// set closure
var arr = [];
for (var i=0;i<3;i++){
var obj = {};
var lex = i + ' dayo';
obj.func = function (){ return lex };
arr.push(obj);
}
// and see what happens...
for (var i=0; i<arr.length; i++){
alert( arr[i].func() );
}
</script>
実行結果
2 dayo 2 dayo 2 dayo
あらびっくり。ActionScript (macromedia) が悪いんじゃないみたい。というわけで、結論:
ActionScript / Javascript 等のECMA準拠スクリプトでは、ループ中にクロージャ作る際は横着するべからず。
関連情報
- torus solutions! - Flash でクロージャ。
- naoyaのはてなダイアリー - Perl のクロージャ
Comments:5
- foobar 2006-07-09 (日) 23:12
-
setFuncにはlexの値がわたされていて、無名関数のときはlexへの参照がわたされている感じじゃないでしょうか。
- passerby 2006-07-10 (月) 13:34
-
perl の場合、for 文、while 文等はスコープを作ります (ループごとに新しい環境が作られる) ので OK なんですが、ECMA スクリプトでは作られないんですよね。これがハマる原因です。
もう少し言うと、クロージャが実行されるのは for ループが回りきった「後」ですから、どうしても「2」という、ループの最後の値が表示されてしまうわけです。
こういう場合、スコープを作ってやる方法があります:
for (var i=0; i (function(){
var obj = {};
var lex = i + ' dayo';
obj.func = function (){ return lex };
arr.push(obj);
})();ECMA スクリプトにおいては、スコープを作るのは function だけなんですよ。したがって、上のように無名関数でスコープを作ってやることで、外部関数に依ることなく perl と同様の事が実現できます。
- bashi 2006-07-10 (月) 20:49
-
>passerbyさん
うおう、無理やりスコープ作るのに、そんな方法あるんですねー。はー(感嘆
有用なアドバイスどうもありがとうございますー。 - bashi 2006-07-11 (火) 10:20
-
冷静にエントリ読み直してみると,単にjavascriptのスコープ範囲の認識が無くてハマってただけですねl。エントリタイトル「ECMAスクリプトのスコープについて」とかで良かったなぁ。クロージャ関係ないさ。己の未熟さに反省... orz
- さいとうなゆた 2008-06-23 (月) 09:13
-
したのような手順でプログラムを書いてみたんですが
これもループ中のクロージャってやつですよね?function setC()
{
// set closure
var arr = [];
for (var i = 0; i {
var lex = i + ' dayo';
arr[i] = lex;
}
return arr;
}func = setC;
// and see what happens..
trace( func()[0] );