for 文の let 宣言のスコープの謎 ~どうもシンプルに while に直すことができないらしい~
はじめに
for 文は while に直すことができると聞きますが 例えば次の様な コードがあったとします。
for (let i = 0, imax = 5; i < imax; i++) {
console.log('c', i);
setTimeout(() => console.log('t', i));
}
結果は次の様になります
c 0
c 1
c 2
c 3
c 4
t 0
t 1
t 2
t 3
t 4
うまくいかない while への直し方
while に直してみましょう
こうでしょうか?
{
let i = 0, imax = 5;
while (i < imax) {
console.log('c', i);
setTimeout(() => console.log('t', i));
i++;
}
}
c 0
c 1
c 2
c 3
c 4
t 5
t 5
t 5
t 5
t 5
どうも i
の 宣言を while の外にしてしまうと スコープが広すぎて setTimeout に渡している変数の値が動的に変わって最終値になってしまうみたいです。
うまくいった while への直し方
つまりこれと同じ
{
let i_ = 0, imax = 5;
while (i_ < imax) {
let i = i_;
console.log('c', i);
setTimeout(() => console.log('t', i));
i_++;
}
}
c 0
c 1
c 2
c 3
c 4
t 0
t 1
t 2
t 3
t 4
よしうまくいきました。
おわりに
つまり for は while にシンプルに直すことはできない でした。
実態のスコープ的には for 文中の let 宣言しても何かシンプルじゃないスコープ(ブロック一枚噛ませてる?)があるのでは?といった感じがありました。
以上。
余談
もしも for 文で var
で宣言すると次の様になります。
for (var i = 0, imax = 5; i < imax; i++) {
console.log('c', i);
setTimeout(() => console.log('t', i));
}
c 0
c 1
c 2
c 3
c 4
t 5
t 5
t 5
t 5
t 5
実行結果は予想の通りです。i
の宣言 が function
の先頭になる(つまりfunction scopeな)ので for の外で宣言したのと同等となり、こうなります。かわいいですね。
余談 その 2
for 文の説明自体は 14.7.4.2 Runtime Semantics: ForLoopEvaluation にあるのですが、
var
宣言じゃない方の 手動 for 文 ForStatement : for ( Expressionopt ; Expressionopt ; Expressionopt ) Statement
と
var
宣言の方の 手動 for 文 ForStatement : for ( var VariableDeclarationList ; Expressionopt ; Expressionopt ) Statement
と明示的にわけているところをみるとスコープ回りの挙動をそこで制御しているのかなといった感じはします。
ForStatement : for ( Expressionopt ; Expressionopt ; Expressionopt ) Statement
- If the first Expression is present, then
a. Let exprRef be ? Evaluation of the first Expression.
b. Perform ? GetValue(exprRef).- If the second Expression is present, let test be the second Expression; otherwise, let test be empty.
- If the third Expression is present, let increment be the third Expression; otherwise, let increment be empty.
- Return ? ForBodyEvaluation(test, increment, Statement, « », labelSet).
ForStatement : for ( var VariableDeclarationList ; Expressionopt ; Expressionopt ) Statement
- Perform ? Evaluation of VariableDeclarationList.
- If the first Expression is present, let test be the first Expression; otherwise, let test be empty.
- If the second Expression is present, let increment be the second Expression; otherwise, let increment be empty.
- Return ? ForBodyEvaluation(test, increment, Statement, « », labelSet).
Discussion