JavaScriptの非同期処理とスコープの関係性について
今回非同期処理とスコープの関係性について紹介したいと思います。
唐突ですが以下のコードをご覧になってください。
function countdown() { let i; for (i = 5; i >= 0; i--) { setTimeout(function() { console.log(i === 0 ? "GO!" : i); }, (5 - i) * 500); } } countdown();
出力結果はどうなると思いますか?
結果は以下になります。
このコードのポイントとしては、変数i
がfor文の外にあるということです。setTimeoutが実行される時にはi
の値が-1
になっているのでsetTimeoutが参照する値が2行目のi
つまり-1になります。
では以下のコードではどうなるのでしょう?
function countdown() { for (let i = 5; i >= 0; i--) { setTimeout(function() { console.log(i === 0 ? "GO!" : i); }, (5 - i) * 500); } } countdown();
結果は以下となります。
こちらは意図としたように動きます。
先ほどのコードとの違いは、変数i
をforループの制御文の箇所で利用しています。
JavaScriptの処理系はループの各ステップで新しい独立した変数iのコピーを作成します。setTimeoutに渡された関数が実行されるときに、自分の独自のスコープになる変数から値を受け取ることになります。
言葉ではなかなかわかりづらいので下記のコードをみてみてください。
// 1回目 { let i = 5 setTimeout(function() { console.log(i === 0 ? "GO!" : 5); // 5 }, (5 - 5) * 500); } // 2回目 { let i = 4 setTimeout(function() { console.log(i === 0 ? "GO!" : 4); // 4 }, (5 - 4) * 500); } // 3回目 { let i = 3 setTimeout(function() { console.log(i === 0 ? "GO!" : 3); // 3 }, (5 - 3) * 500); } // 4回目 { let i = 2 setTimeout(function() { console.log(i === 0 ? "GO!" : 2); // 2 }, (5 - 2) * 500); } // 5回目 { let i = 1 setTimeout(function() { console.log(i === 0 ? "GO!" : 1); // 1 }, (5 - 1) * 500); } // 6回目 { let i = 0 setTimeout(function() { console.log(i === 0 ? "GO!" : 0); // "GO!" }, (5 - 0) * 500); }
setTimeout関数が実行される時には上記のように各ステップで新しくコピーされた変数i
を参照していることになります。
まとめ
どうしてこのような結果になるのか最初理解するのに時間が掛かりました。JavaScriptにおける非同期処理とスコープについてはちゃんと理解しておく必要があります。 間違いがあれがご指摘ください!