那是因为,在 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 混合,我们甚至可以做出第三种选择:
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
andj
声明移到了范围的顶部,只是为了便于阅读。它具有与 相同的效果for (var i = [...]
,将由解释器提升。