Home > ActionScript > ActionScript - ループ中でクロージャ作る際の注意点

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準拠スクリプトでは、ループ中にクロージャ作る際は横着するべからず。

関連情報

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] );

Comment Form

コメントを表示する前にこのブログのオーナーの承認が必要になることがあります。

Remember personal info

Home > ActionScript > ActionScript - ループ中でクロージャ作る際の注意点

Search
Feeds

Page Top