javascriptのクロージャはおかしい

Scheme2jsは強力です。リストでいろいろ簡潔に書けるし。
本当は使いたいけど、それだと普通の人に理解されなくなるので、真っ当にjavascriptを勉強することにしました。
そうしたら以下に自分もハマりました。
javascriptクロージャっておかしいんじゃないの?
http://labs.unoh.net/2006/05/post_11.html

ループ内の無名関数

スーパーなエンジニアの近くに座っているMODです。
こないだJavascriptを書いててハマったネタをひとつ。

for文等繰り返し文の中で無名関数を定義するとき、定義内でインデックスの変数の値を用いることができないらしいです。
例えばこんなコード

<input type='button' id='wan1' value='わん'>
<input type='button' id='wan2' value='わんわん'>
<script type="text/javascript">
for (i=1; i<=2; i++) {
    elem = document.getElementById('wan'+i);
    elem.attachEvent('onclick', function(){alert('いぬ No.'+i);});
}
</script>

※このコードはIEのみ対応です。

ボタンをクリックすればそれぞれ「いぬ No.1」、「いぬ No.2」とアラートで表示させる意図ですが、実際にはどっちをクリックしても表示されるのは「いぬ No.3」という文字列。
どうやら i の値は関数を呼び出した時点で評価されているようです。
実行時に関数に渡されるイベントオブジェクトを用いてなんとかするしか今のところ解決策がみつかっていません。Javascript難しいです。

これならどうでしょうか?

for (i=1; i<=2; i++) {
elem = document.getElementById('wan'+i);
elem.attachEvent('onclick', createHandler(i));
}
function createHandler(index) {
return function(){ alert('いぬ No.'+index); };
}

投稿者: cesare | 2006年05月18日 21:17

elem.attachEvent('onclick', (function(i){return function(){alert('いぬ No.'+i);}}).call(this, i));

これでうまくいきますね。
最初の無名関数は関数オブジェクトを返すための無名関数です。
その場で実行したいので、call を用いて引数 i を渡して受け取った引数を元に生成した関数オブジェクトを返しています。

原理的に cesare さんとやってることは同じですが、
全部無名関数で実行できます。

投稿者: masato | 2006年05月19日 02:14

>cesareさん
コメント&トラックバックありがとうございます!
直接関数への参照を置くと後から実行されるから意図どおりの値になりませんが、attachEventが呼ばれた際に引数の中で関数を実行すれば、適切な値が反映された無名関数が返るということですね。
Javascriptに慣れていないこともあって、関数オブジェクトを戻り値にする関数という発想が浮かびませんでした。勉強させていただきました。

>masatoさん
理解するのに時間がかかりました。もう、何やら別の言語と化してる気がします。でも多分これがJavascriptなんですね。
最初の無名関数の仮引数はnとかでもよいというのがミソでしょうか。
javascriptのcallも知りませんでした・・・。自分勉強不足です。__o_

投稿者: MOD | 2006年05月19日 13:46

更に別解

elem.attachEvent('onclick', new Function("", "alert('いぬ No.'+"+i+");"));

Javascript では関数というのは単なるオブジェクトであるということ、
function(){} という書き方は Function オブジェクトを new するためのリテラルでしかないというのがポイント。

ていうかこっちの方が簡潔やね

投稿者: masato | 2006年05月19日 17:26

以上、抜粋。
さらに自分の解。

<script type="text/javascript">
for (i=1; i<=2; i++) {
        hoge(i);
}
function hoge(i) {
    elem = document.getElementById('wan'+i);
    elem.attachEvent('onclick', function(){alert('いぬ No.'+i);});       
}
</script>

いまなら、prototype.jsで $() とか Event.observe です。

<html>
<head>
<script src="prototype.js" type="text/javascript"></script>
</head>
<body>
<input type='button' id='wan1' value='wan'>
<input type='button' id='wan2' value='wanwan'>
<script type="text/javascript">
for (i=1; i<=2; i++) {
    hoge(i);
}
function hoge(i) {
    Event.observe('wan'+i, 'click', function(){alert('inu No.'+i);});
}
</script>
</body>
</html>

入門系のページでは、無名関数がjavascriptの深淵と書いてあったりして。
Scheme使いは肩身がせまい。