このコードはログ6
、6回:
(function timer() {
for (var i=0; i<=5; i++) {
setTimeout(function clog() {console.log(i)}, i*1000);
}
})();
しかし、このコード...
(function timer() {
for (let i=0; i<=5; i++) {
setTimeout(function clog() {console.log(i)}, i*1000);
}
})();
...次の結果をログに記録します。
0
1
2
3
4
5
どうして?
これは、let
が各項目の内部スコープに異なる方法でバインドし、var
がi
の最新の値を保持するためですか?
var
を使用すると、関数スコープがあり、すべてのループ反復で1つの共有バインディングのみが使用されます。つまり、すべてのsetTimeoutコールバックのi
は同じ変数finallyは、ループの反復が終了すると6に等しくなります。
let
を使用すると、ブロックスコープがあり、for
ループで使用すると、各反復の新しいバインディングが取得されます。つまり、すべてのsetTimeoutコールバックのi
は異なる変数で、それぞれが異なる値を持っています:最初のものは0、次のものは1などです。
したがって、この:
(function timer() {
for (let i = 0; i <= 5; i++) {
setTimeout(function clog() { console.log(i); }, i * 1000);
}
})();
varのみを使用したこれと同等です。
(function timer() {
for (var j = 0; j <= 5; j++) {
(function () {
var i = j;
setTimeout(function clog() { console.log(i); }, i * 1000);
}());
}
})();
ブロックスコープがlet
を使用した例で機能するのと同様の方法で、関数スコープを使用するためにすぐに呼び出される関数式を使用します。
j
名を使用せずに短くすることもできますが、おそらくそれほど明確ではありません。
(function timer() {
for (var i = 0; i <= 5; i++) {
(function (i) {
setTimeout(function clog() { console.log(i); }, i * 1000);
}(i));
}
})();
そして、矢印機能でさらに短く:
(() => {
for (var i = 0; i <= 5; i++) {
(i => setTimeout(() => console.log(i), i * 1000))(i);
}
})();
(ただし、矢印関数を使用できる場合は、var
を使用する理由はありません。)
これは、Babel.jsがlet
が使用できない環境で実行するためにlet
を使用して例を変換する方法です。
"use strict";
(function timer() {
var _loop = function (i) {
setTimeout(function clog() {
console.log(i);
}, i * 1000);
};
for (var i = 0; i <= 5; i++) {
_loop(i);
}
})();
コメントにBabel.jsへのリンクを投稿していただいた Michael Geary に感謝します。コメント内のリンクを参照して、コード内のすべてを変更し、変換がすぐに行われるのを見ることができるライブデモを確認してください。他のES6機能がどのように翻訳されるかを見るのは興味深いです。
技術的には、@ rspが彼の優れた答えで説明する方法です。これが、内部で機能することを理解する方法です。 var
を使用するコードの最初のブロック
(function timer() {
for (var i=0; i<=5; i++) {
setTimeout(function clog() {console.log(i)}, i*1000);
}
})();
コンパイラーがforループ内でこのようになると想像できます
setTimeout(function clog() {console.log(i)}, i*1000); // first iteration, remember to call clog with value i after 1 sec
setTimeout(function clog() {console.log(i)}, i*1000); // second iteration, remember to call clog with value i after 2 sec
setTimeout(function clog() {console.log(i)}, i*1000); // third iteration, remember to call clog with value i after 3 sec
等々
i
はvar
を使用して宣言されているため、clog
が呼び出されると、コンパイラーは変数i
をtimer
である最も近い関数ブロックで見つけます。すでにfor
ループの終わりに達し、i
は値6を保持し、clog
を実行します。つまり、6が6回ログに記録されることになります。