var を使った場合
'use strict'
for (var i = 0; i < 10; ++i) {
setTimeout(function(argument) {
console.log(i)
}, i * 1000);
}
> node test
10
10
10
10
10
10
10
10
10
10
let を使った場合
'use strict'
for (let i = 0; i < 10; ++i) {
setTimeout(function(argument) {
console.log(i)
}, i * 1000);
}
> node test
0
1
2
3
4
5
6
7
8
9
なぜこうなるのか?
varは、その宣言を行った関数のブロックが有効範囲になります。関数のブロック内でない場所で宣言した場合は、グローバル変数になります(実行環境によってはグローバル変数ではなくモジュール内のローカル変数になったりする場合もありますが)。
'use strict'
for (var i = 0; i < 10; ++i) {
}
var num = 123;
console.log(i); // -> 10
これは、下記のように宣言するのと同じになります。
'use strict'
var i;
var num;
for (i = 0; i < 10; ++i) {
}
num = 123;
console.log(i); // -> 10
つまり、冒頭の例
'use strict'
for (var i = 0; i < 10; ++i) {
setTimeout(function(argument) {
console.log(i)
}, i * 1000);
}
は、
'use strict'
var i;
for (i = 0; i < 10; ++i) {
setTimeout(function(argument) {
console.log(i)
}, i * 1000);
}
というのと同じということになります。iはループが終了した時点で10になるので、setTimeoutの中のconsole.log(i)は10を出力します。
これを0,1,2,3,...という出力にしたい場合は、以下のように関数で囲って、iを引数として扱うようにします。
'use strict'
for (var i = 0; i < 10; ++i) {
(function(i) { // *1
setTimeout(function(argument) {
console.log(i)
}, i * 1000);
})(i);
}
上記ソース中の*1とコメントを付けた箇所のiは、ループの中で宣言された関数の引数であり、その上の行のfor(var iの箇所にある変数iとは別物です。
わかりやすくするために引数名を書き換えるとこのようになります。
'use strict'
for (var i = 0; i < 10; ++i) {
(function(arg) {
setTimeout(function(argument) {
console.log(arg)
}, i * 1000);
})(i);
}
引数を使わずに別の変数に代入するという方法もあります。
'use strict'
for (var idx = 0; idx < 10; idx++) {
(function() {
var i = idx; // こうすると、iはこの関数ブロックのローカル変数になる
setTimeout(function() {
console.log(i);
}, i * 1000);
})()
}
let
varとは違い、letは宣言された箇所を含む{ ... }で囲まれた範囲のブロックが有効範囲になります。
'use strict'
let a;
for (let i = 0; i < 10; ++i) {
// a が使える
// i が使える
}
// a が使える
// i は使えない!
厳密には異なるのかもしれませんが、下記のコードは
'use strict'
for (let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
このように書くのと同じです。
'use strict'
for (var idx = 0; idx < 10; idx++) {
(function() {
var i = idx;
setTimeout(function() {
console.log(i);
}, i * 1000);
})()
}
letの方が理に適った動作だと思います。Node.jsなどES6の機能が使えるJavaScript実行環境では(違いがあることを把握したうえで)積極的にletの方を使って行くのが良いでしょう。