もう5年も前にコチラにて解説されつくしていることなのですが、さっきまでJavascriptの奇妙な変数スコープ仕様にハマってました。
http://d.hatena.ne.jp/m-hiyama/20051209/1134086113
何にはまっていたかというと、とりあえず下のコードを見てください。
<html> <body> <script type="text/javascript"> for (/* int */ var i = 1; i <= 5; i++) { window.setTimeout(function() { alert(i); }, 1000); } </script> </body> </html>
この実行結果はどうなると思いますか?
はい、トリックが分かっているそこのアナタ。
この記事をコレ以上読んでも、あなたの役立つことは載ってませんので、今日はお引き取りくださいませ。
どうもお疲れ様でした。
答え
まず、ダイアログが5回出るというのはOKですよね、さすがに。
で、答えは「5、5、5、5、5」と5が5回連続で表示されます。
嘘でしたすみません。「6, 6, 6, 6, 6」と6が5回連続で表示されます。
「1、2、3、4、5が順不同で表示される」ではありません。
なぜかというと、
詳しくは上のリンク先を読んでいただきたいのですが、簡単にいうと
for (/* int */ var i = 1; i <= 5; i++) { window.setTimeout(function() { alert(i); }, 1000); }
は、
var int i; for (i = 1; i <= 5; i++) { window.setTimeout(function() { alert(i); }, 1000); }
と等価だから。つまり、
window.setTimeout(function() { alert(i); }, 1000);
は外部のスコープ?にある変数iの値をキャプチャしてくれないのです。
1000ミリ秒経過する前にループは回りきり、iの値が5になったままでsetTimeoutした関数が呼ばれる。
だからalert(5);
alert(6);
が5回呼ばれるだけ、というわけ。
Javascriptはようわからん、というアナタのために
Java版のサンプルコードをご用意して御座います。
final int[] i = { 1 }; for (; i[0] < 5; i[0]++) { new Thread() { public void run() { Thread.sleep(1000); System.out.println(i[0]); } }.start(); }
こんな時はどうすればよいのか
変数をキャプチャするにはクロージャを使います。要するに
for (/* int */ var i = 1; i <= 5; i++) { (function() { var k = i; window.setTimeout(function() { alert(k); }, 1000); })(); }
とし、ローカル変数kにiの値をバインドさせればOK。
これで「1、2、3、4、5」と表示できます。