孤独プログラマー譚

孤独死が近い。

ECMAScript6 let・ループ・ブロックスコープ

ES5以下がだいだい分かってきたような気がしたから、ES6に手を出していくことにした。

以下の挙動が意味不明だった。

var callback = [];
for (let i = 0; i < 3; i++) {
  callback[i] = function () {
    console.log(i);
  };
}
callback[0](); // 0
callback[1](); // 1
callback[2](); // 2

だいだい、letはブロックスコープ内で再定義できないはずなのに、何でループできてるのか…?

ついでに、let を var に変えるとこうなる。

var callback = [];
for (var i = 0; i < 3; i++) {
  callback[i] = function () {
    console.log(i);
  };
}
callback[0](); // 3
callback[1](); // 3
callback[2](); // 3

変数iはグローバル変数なので、関数実行時の値が参照される。

0, 1, 2 ... としたい場合、ES5なら以下のように書く。

var callback = [];
for (var i = 0; i < 3; i++) {
  (function (i) {
    callback[i] = function () {
      console.log(i);
    };
  }(i));
}
callback[0](); // 0
callback[1](); // 1
callback[2](); // 2

即時関数に引数から変数iを渡す際、即時関数内で変数iが新たに定義される。
グローバルの変数iと、即時関数スコープ内の変数iは別物となる。
ついでに言うと、callback配列内の各関数はクロージャとなる。変数iは、各関数ごとに独立している。

話は初めのletに戻るが、そもそもループの解釈を勘違いしていた。
以下のように、ループ毎に、新たにブロックスコープが作られるイメージが正しいと思う。

{
  let i = 1;
  function callback1() {
    console.log(i);
  }
}
{
  let i = 2;
  function callback2() {
    console.log(i);
  }
}
callback1(); // 1
callback2(); // 2

ループ毎にletが定義されていると考えると、初めの挙動にも納得がいく。