◆ let + for 文にすると関数を内部で作ってもループ変数は各回で固定なので大丈夫です
◆ JavaScript では while で for と全く同じ動きするものは作れない?

for 文のループで関数を登録すると 関数を実行したときはループ変数が最後になってるので思い通りに動かないというのは有名なハマりどころですよね

let のブロックスコープだとこの問題は起きない?
ループ変数はインクリメントしたりするのでループの各ブロックで共有しているから無理?

と気になったので調べてみます

let のスコープ


var
var fns = []
for(var i=0;i<3;i++){
fns[i] = function(){console.log(i)}
}
fns.forEach(e => e())
3
3
3

let
var fns = []
for(let i=0;i<3;i++){
fns[i] = function(){console.log(i)}
}
fns.forEach(e => e())
0
1
2

ちゃんと 0 1 2 になってます
ということはループ変数はループの各ブロックで別物?

while に直すと

for は while にするとこうなるんだと思ってました
for(var i=0;i<3;i++){
// for の中身
}
{
let i = 0
while(i < 3){
{
// for の中身
}
i++
}
}

i++ の上にもう一つスコープがあるのは i++ のところに for の内側で宣言したものを使えないからです
for(var i=0;i<10;i+=x){
let x = i * 2
}
これを実行すると

x is not defined

って言われますよね


この while だと while の外側に let があるので 全ループでループ変数は共通です
なので 関数を登録しても
var fns = []
{
let i = 0
while(i < 3){
{
fns[i] = function(){console.log(i)}
}
i++
}
}
fns.forEach(e => e())
3
3
3
こうなります


for 文のように 0 1 2 と表示させるようにするには let i の宣言を while の内側に入れないとだめです
ですが そうすると インクリメントできません

余計な変数が見えますが こういうことをすればできます
var fns = []
{
let _counter = 0
while(_counter < 3){
let i = _counter
{
fns[i] = function(){console.log(i)}
}
_counter++
}
}
fns.forEach(e => e())
0
1
2

_counter の変数をなくせないので スコープも含めて全く同じ動きを while でさせるのは不可能そうです

関数だと

なんとなく関数で for を作ってみました
var fns = []
let i = 0
;[i] = function recur(i){
if(i<3){
{
fns[i] = function(){console.log(i)}
}
return recur(i + 1)
}else{
return [i]
}
}(i)
fns.forEach(e => e())
いろいろと柔軟性にかけてそうです

const

ついでなので const でやってみると 当たり前ですが for の3 つ目のブロックのインクリメント部分で代入できないエラーです
変更しなければ const でも問題なく使えます

ところで

他の言語では while で for と同じものができたような記憶があります
もしかしてこの特殊な動きは JavaScript で楽できるような特殊仕様であって 一般のブロックスコープではない?と思って別の言語で試してみようと思います

スクリプト言語でブロックスコープって聞かないので 静的型付け言語で JavaScript っぽさのある c# で試してみます
var list = new List<Action> { };
for(var i = 0; i < 3; i++)
{
  list.Add(() => Console.WriteLine(i));
}
list.ForEach(f => f());
3
3
3

3 ですね

やっぱりこの動きが自然なんですよ
JavaScript の let は特殊!!