(注:このブログはもう更新していません)この日記は私的なものであり所属会社の見解とは無関係です。 GitHub: takahashikzn

[クラウド帳票エンジンDocurain]

Javascriptのスコープは謎仕様

もう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」と表示できます。


しっかし、

Javascriptの作者は、なんでこんな設計にしたんでしょうねぇ。

こんな謎仕様で幸せになる人って、せいぜいコンパイラの作者だけ。
ここは手抜きしちゃいかんトコだろ…