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
の方を使って行くのが良いでしょう。