那是因爲,在JavaScript中,var
有函數範圍。
var
聲明將被懸掛到當前執行上下文的頂部。也就是說,如果它位於函數內部,var
將在函數的執行上下文範圍內,否則是程序(全局)執行上下文。
ECMAScript 2015(又名ES6)引入了let
它可以讓你創建塊範圍變量,但是因爲它沒有得到廣泛的支持,所以我只是留下鏈接供參考。
一種變通方法,仍用var
,並將它「範圍的」環路內,是創建一個新的執行上下文,也知道作爲closure:
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
時的值。
請參閱Live demo。
做同樣事情的另一種方法是在for
循環內創建一個IIFE。這通常比較容易閱讀,但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
內一個新的執行上下文。 (Demo)
混合上述IIFE第一種方法(callbackFactory
),我們甚至可以做一個第三選擇:
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
循環內創建功能,這對性能不利,但只是注意到這也是可能的,並且works。
這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 */
Fiddle
注意,我我已經將i
和j
聲明移到了範圍的頂部,僅爲了便於閱讀。它與for (var i = [...]
具有相同的效果,將由翻譯人員提起。
[JavaScript變量範圍](http://stackoverflow.com/questions/500431/javascript-variable-scope)。另請參見:[如何將JS變量的值*傳遞給函數?](http://stackoverflow.com/questions/2568966/how-do-i-pass-the-value-not-the-參考-JS-variable-to-a-function) – DCoder
極好的參考!我以爲JavaScript有一個塊範圍,但顯然我只是在思考JavaScript的錯誤導致將是理智的;) –
編輯我的答案,實際回答你的問題,看最後一個片段在底部。 –