これは、JavaScriptではvar
関数スコープがあるためです。
var
宣言は、現在の実行コンテキストの先頭まで引き上げられます。つまり、関数内にある場合はvar
、関数の実行コンテキスト内にスコープが設定されます。それ以外の場合は、プログラム(グローバル)実行コンテキストにスコープが設定されます。
ECMAScript 2015(別名ES6)let
では、ブロックスコープ変数を作成できるようになっていますが、広くサポートされていないため、参照用にリンクを残しておきます。
引き続き使用var
してループ内で「スコープ」を設定するための回避策は、クロージャーとも呼ばれる新しい実行コンテキストを作成することです。
function callbackFactory(i, j) {
// Now `i` and `j` are scoped inside each `callbackFactory` execution context.
return function() { // This returned function will be used by the `setTimeout`.
// Lexical scope (scope chain) will seek up the closest `i` and `j` in parent
// scopes, that being of the `callbackFactory`'s scope in which this returned
// function has been initialized.
console.log("in timeout i is: " + i + " j is: " + j);
};
}
for(var i = 0; i < 5; i++) {
var j = i + 10;
console.log("i is: " + i + " j is: " + j);
setTimeout( callbackFactory(i, j), i * 1000);
}
両方i
とj
コールバックスコープ内をスコープsetTimeout
したので、に渡されたときと同じ値を内に返しますcallbackFactory
。
ライブデモを参照してください。
同じことを行う別の方法は、ループ内にIIFEfor
を作成することです。これは通常読みやすいですが、JS(H | L)intはあなたに怒鳴ります。;)
これは、ループ内に関数を作成するとパフォーマンスが低下すると見なされるためです。
for(var i = 0; i < 5; i++) {
var j = i + 10;
console.log("i is: " + i + " j is: " + j);
(function(i, j) { // new execution context created for each iteration
setTimeout(function() {
console.log("in timeout i is: " + i + " j is: " + j);
}, i * 1000);
}(i, j)); // the variables inside the `for` are passed to the IIFE
}
for
上記では、各反復で新しい実行コンテキストを作成しました。(デモ)
最初のアプローチ(callbackFactory
)と上記のIIFEを組み合わせて、3番目のオプションを作成することもできます。
for(var i = 0; i < 5; i++) {
var j = i + 10;
console.log("i is: " + i + " j is: " + j);
setTimeout(function(i, j) {
return function() {
console.log("in timeout i is: " + i + " j is: " + j);
};
}(i, j), i * 1000);
}
これは、関数の代わりにIIFEを使用しているだけcallbackFactory
です。これは読みやすいとは思えませんが、for
ループ内に関数を作成するため、パフォーマンスが低下しますが、これも可能であり、機能することに注意してください。
これらの3つのアプローチは、野生で非常に一般的に見られます。=]
ああ、主な質問に答えるのをほとんど忘れていました。callbackFactory
をループと同じスコープに配置し、for
ループの内側をスコープする代わりに、スコープチェーンに外側のスコープをi
シークさせます。i
(function() {
var i, j;
function callbackFactory(j) {
// the `j` inside this execution context enters it as a formal parameter,
// shadowing the outer `j`. That is, it is independent from the outer `j`.
// You could name the parameter as "k" and use "k" when logging, for example.
return function() {
// Scope chain will seek the closest `j` in parent scopes, that being
// the one from the callbackFactory's scope in which this returned
// function has been initialized.
// It will also seek up the "closest" `i`,
// which is scoped inside the outer wrapper IIFE.
console.log("in timeout i is: " + i + " j is: " + j);
};
}
for(i = 0; i < 5; i++) {
j = i + 10;
console.log("i is: " + i + " j is: " + j);
setTimeout( callbackFactory(j), i * 1000);
}
}());
/* Yields:
i is: 0 j is: 10
i is: 1 j is: 11
i is: 2 j is: 12
i is: 3 j is: 13
i is: 4 j is: 14
in timeout i is: 5 j is: 10
in timeout i is: 5 j is: 11
in timeout i is: 5 j is: 12
in timeout i is: 5 j is: 13
in timeout i is: 5 j is: 14 */
フィドル
i
読みやすさのためだけに、とj
宣言をスコープの一番上に移動したことに注意してください。for (var i = [...]
通訳が持ち上げるのと同じ効果があります。