for 文の let 宣言のスコープの謎 ～どうもシンプルに while に直すことができないらしい～

# はじめに
for 文は while に直すことができると聞きますが 例えば次の様な コードがあったとします。

```js
for (let i = 0, imax = 5; i < imax; i++) {
  console.log('c', i);
  setTimeout(() => console.log('t', i));
}
```

https://www.typescriptlang.org/play/?ssl=4&ssc=2&pln=1&pc=1#code/GYewTgBAFANgpgFwgSwgXggBgDQoLYCGAHuhAKwDcKEAPPsVcgNRMCUEA3gLABQEEAYxAA7AM4h4AOhggA5lADkAhbmSsKvfqMQAVZHjggArgihR2aAHyCR4qTPkKEKlK3W8AvkA

結果は次の様になります

```output
c 0
c 1
c 2
c 3
c 4
t 0
t 1
t 2
t 3
t 4
```



# うまくいかない while への直し方

while に直してみましょう

こうでしょうか？

```js
{
  let i = 0, imax = 5;
  while (i < imax) {
    console.log('c', i);
    setTimeout(() => console.log('t', i));
    i++;
  }
}
```

https://www.typescriptlang.org/play/?#code/N4WAUABBA2CmAuECWEC8EAMAaZBbAhgB5oQCsA3OFAO4AWScEAFCgDx5ECUEokUEAYwD2AOwDOQuADpoQgOZMA5AMU4knSnyhiEAFSS5YQgK7wmTbqgB8g0ROmyFi+KuScNVfkgDU3zVABfcACgA

```output
c 0
c 1
c 2
c 3
c 4
t 5
t 5
t 5
t 5
t 5
```


どうも `i` の 宣言を while の外にしてしまうと スコープが広すぎて setTimeout に渡している変数の値が動的に変わって最終値になってしまうみたいです。

# うまくいった while への直し方

つまりこれと同じ

```js
{
  let i_ = 0, imax = 5;
  while (i_ < imax) {
    let i = i_;
    console.log('c', i);
    setTimeout(() => console.log('t', i));
    i_++;
  }
}
```

https://www.typescriptlang.org/play/?#code/N4WAUABBA2CmAuECWB9CBeCAGANMgtgIYAeGEArANzhQDuAFknBABSoQA8BJAlBKJCgwEyMqmqCoAYwD2AOwDOMuADpoMgOYsA5FO14kPCUIgKEAFST5YMgK7wWLPugB8EWYuWw1mnfH3IPEY0QqgA1GHGEAC+4NFAA

```output
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` で宣言すると次の様になります。

```js
for (var i = 0, imax = 5; i < imax; i++) {
  console.log('c', i);
  setTimeout(() => console.log('t', i));
}
```

https://www.typescriptlang.org/play/?#code/GYewTgBAFAbghpAlhAvBADAGgogtnAD1QgFYBuHCAHh3wIsQGpGBKCAbwFgAoCCAYxAA7AM4gANgFMAdOJABzKAHJ+S7IhZkefEZIAuAFTySQAVz1QobFAD4BwsVNkLletThaaeAXyA

```output
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** にあるのですが、

https://tc39.es/ecma262/multipage/ecmascript-language-statements-and-declarations.html#sec-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
> 1. If the first Expression is present, then
>   a. Let exprRef be ? Evaluation of the first Expression.
>   b. Perform ? GetValue(exprRef).
> 2. If the second Expression is present, let test be the second Expression; otherwise, let test be empty.
> 3. If the third Expression is present, let increment be the third Expression; otherwise, let increment be empty.
> 4. Return ? ForBodyEvaluation(test, increment, Statement, « », labelSet).

https://tc39.es/ecma262/multipage/ecmascript-language-statements-and-declarations.html#prod-qUCVnDNm

> ForStatement : for ( var VariableDeclarationList ; Expressionopt ; Expressionopt ) Statement
> 1. Perform ? Evaluation of VariableDeclarationList.
> 2. If the first Expression is present, let test be the first Expression; otherwise, let test be empty.
> 3. If the second Expression is present, let increment be the second Expression; otherwise, let increment be empty.
> 4. Return ? ForBodyEvaluation(test, increment, Statement, « », labelSet).

https://tc39.es/ecma262/multipage/ecmascript-language-statements-and-declarations.html#prod-HwfL-Win