先日(といってもすでに半年前…)、HiroshimaRB にて「思ったようにコードが動かない!」という題目で LT をしたのですが、JavaScript のクロージャーのところで質問がありました。私にもっと理解力があって、冷静に対応できればよかったのですが、ぐでぐでな対応になってしまったので、反省を込めてここに記す(汗
- 問題のコード
サンプルコードはこのようなものでした。 <html><body> <span id="value" style="font-size: 48pt;" ></span> <script type="text/javascript"> for (i=0; i<5; i++) { f = function() { // i の値を追記する document.getElementById("value").innerHTML += i + "<br>"; } // i 秒後に i を追加表示する(予定) setTimeout("f()", i*1000); } </script> </body></html>期待する結果は、1 秒ごとに 0 → 1 → 2 → 3 → 4 と表示される予定です。 0 
 1
 2
 3
 4しかし実行結果は、 0 → 1 → 2 → 3 → 4、とはならず、すべて 5 で表示されます。 5 
 5
 5
 5
 5
- 指摘の内容
この時、for のところに var を入れればよいのでは、という指摘がありました。 
 指摘の理由は、上記のコードの変数 i スコープがグローバルです。だから値を共有してしまったのではないか、ということでした。
 つまり、上記のコードの以下の部分を、for (i=0; i<5; i++) {以下のように「var」を追加し、変数 i のスコープをグローバルからローカルに変えれぱ解決するのではないか?という指摘です。 for (var i=0; i<5; i++) {しかし、これでは結果は変わらず、期待した動作にはなりませんでした。 
- グローバル変数とローカル変数
ところで、JavaScript では、なにも宣言せずに使用を始めた変数というのはグローバル変数相当(Window(=this) のメンバー) になります。(*1) 
 試してみます。<html> <body> <form> <script> function click1() { x = "aaa"; alert(x); } function click2() { alert(x); alert(this.x); alert(window.x); } </script> <input type="button" value="ボタン1" onClick="click1()"> <input type="button" value="ボタン2" onClick="click2()"> </form> </body> </html>ボタン1 を押すと、"aaa" で alert が表示されます。 
 その後、ボタン2 を押すと、click2() では x は宣言や代入をしていないはずなのに "aaa" で alert が表示されます。
 つまり click1() で代入した x のスコープはローカル変数ではなく、グローバル変数に相当することがわかります。x をローカル変数にするためには、代入文の前に var をつけます。(*2) 
 こうすると、click1() を実行した後に click2() を実行しても、click2() の x は定義されていないため、alert は表示されません。<html> <body> <form> <script> function click1() { var x = "aaa"; alert(x); } function click2() { alert(x); alert(this.x); alert(window.x); } </script> <input type="button" value="ボタン1" onClick="click1()"> <input type="button" value="ボタン2" onClick="click2()"> </form> </body> </html>
- タイマー要素を排して、もう一度眺めてみる
指摘の内容は、 - グローバル変数なので、
- 各 function がグローバル変数を参照(共有)してしまい、
 同じ値(x の値が for を抜けた時の値)になってしまうのではないか?という話でした。この指摘は、半分あたりだとおもいます。 しかし単純にローカル変数にしてもうまくいかないことを試してみます。 
 上記コードではタイマー(SetTimeout)に渡すようになっていますが、事を単純にするため、タイマーは使わないコードに変更します。<html> <body> <form> <script> function click1() { var f = new Array(5); // 仕込 for (var i=0; i<5; i++) { f[i] = function() { alert(i); } } // 実行 f[0](); f[1](); f[2](); f[3](); f[4](); } </script> <input type="button" value="ボタン1" onClick="click1()"> </form> </body> </html>結果は、5 回とも "5" で alert が表示されます。 
- 原因
上記コードの「仕込み」の部分の「alert(i)」の部分に注目します。 
 この alert(i) は for 文のループ中では実行されません。function の宣言(定義)をしているだけなんですね。
 実行はその下の「実行」のところの f[0]()~f[4]() で行っています。alert(i) の i の値がどこで確定(*3)するかというと、function f[i]() の宣言時ではなく、f[0](); の実行時です。 
 実行は for を抜けた後です。forを 抜けた後ということは、当然 i = 5 ですから、alert もすべて 5 で表示されます。
- 対策
f[i] の中の i は、値ではなく変数 i を参照しています。これが原因で、f[i] は実行したときの変数 i の値を表示してしてしまいます。 
 ということは、変数 i を参照するのではなく、for を実行したときの変数 i の"値"を何かしらの方法で保存してやればよいことになります。手順は以下の通り。 
 1.f[i]() を作る処理を g() で囲みます。
 2.g() の中で、ループ変数 i を g() のローカル変数 x に代入します。
 3.for ループの中で、g() を実行します。こうすると、function を使わず直截変数 i を参照しているケースと違って、以下のように動作します。 - g() は for ループ内で実行するので、var x = i により、変数 x に変数 i の値が代入されます。
- g() は終了するとき、x は i の値で固定されます。
- for の 2 ループ目に g(); を実行するときは、変数 x のインスタンスは 1 ループ目のものとは違うものが与えられます。
 #変数 x は g() のスコープにいるので、前回ループで実行した g() での値は引き継がない。
- したがって、f[i] で参照している x は ループ中の i と同じ値になります。(*4)
 つまり、変数 i のスコープは click2() ですが、変数 x のスコープは g() です。したがって変数 x は f[i] では共有されず、それぞれ別々の値(=インスタンス)を保持することになります。 <html> <body> <form> <script> function click2() { var f = new Array(5); for (var i=0; i<5; i++) { g = function() { /* click2()のスコープである i を g() の スコープである x に代入する */ var x = i; f[i] = function() { alert(x); } } g(); // ここで g() の中の x が確定する。 } f[0](); f[1](); f[2](); f[3](); f[4](); } </script> <input type="button" value="ボタン1" onClick="click2()"> </form> </body> </html>
 (*1) 
 Perl もそうみたいです。(ただ、perl は var ではなく my と書くようです)
 VB(VBS や VBA も)は、ルーチン内で代入したものはデフォルトではローカルスコープ。但し、あとから同名のグローバル変数を作るとグローバル化してしまう。
 JavaScript のスコープは結構変態です。(と思う)(*2) 
 実は var は後に記述してもいいらしいです。
 宣言を後に回した記述例。こう書いても x はローカル変数として認識します。
 この点も変態ですね。function click1() { x = "aaa"; alert(x); var x }(*3) 
 正確には i の値を「確定」しているわけではなく、ただ単純に i の値が参照されているだけですが…(*4) 
 function が return すればローカル変数も解放されるはずですが、この場合はされません。
 ローカル変数のキャプチャという機能によって、そのローカル変数を参照している function がいなくなるまで保持されています。