var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
// and store them in funcs
funcs[i] = function() {
// each should log its value.
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
它输出以下内容:
而我希望它输出:
当由于使用事件侦听器而导致功能运行延迟时,会发生相同的问题:
var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
// as event listeners
buttons[i].addEventListener("click", function() {
// each should log its value.
console.log("My value: " + i);
});
}
<button>0</button>
<br />
<button>1</button>
<br />
<button>2</button>
…或异步代码,例如使用 promise :
// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));
for (var i = 0; i < 3; i++) {
// Log `i` as soon as each promise resolves.
wait(i * 100).then(() => console.log(i));
}
在
for in
和for of
循环中也很明显:const arr = [1,2,3];
const fns = [];
for(var i in arr){
fns.push(() => console.log(`index: ${i}`));
}
for(var v of arr){
fns.push(() => console.log(`value: ${v}`));
}
for(var f of fns){
f();
}
这个基本问题的解决方案是什么?
最佳答案
嗯,问题在于每个匿名函数中的i
变量都绑定(bind)到了函数外部的同一变量。
ES6解决方案:let
ECMAScript 6(ES6)引入了新的let
和const
关键字,其作用域与基于var
的变量不同。例如,在具有基于let
的索引的循环中,循环中的每次迭代都会有一个新的具有循环范围的变量i
,因此您的代码将按预期工作。有很多资源,但是我建议2ality's block-scoping post作为重要信息来源。
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
但是请注意,Edge 9之前的IE9-IE11和Edge支持let
,但出现上述错误(它们不会每次都创建一个新的i
,因此上述所有功能都将记录3,就像使用var
一样)。 Edge 14终于正确了。ES5.1解决方案:forEach
随着
Array.prototype.forEach
函数的普及(在2015年),值得注意的是,在那些主要涉及对值数组进行迭代的情况下,.forEach()
提供了一种干净自然的方法来为每次迭代获取不同的闭包。也就是说,假设您有某种包含值的数组(DOM引用,对象等),并且出现了设置针对每个元素的回调的问题,则可以执行以下操作:var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
// ... code code code for this one element
someAsynchronousFunction(arrayElement, function() {
arrayElement.doSomething();
});
});
这个想法是,与.forEach
循环一起使用的回调函数的每次调用都将是其自己的闭包。传递给该处理程序的参数是特定于迭代的特定步骤的数组元素。如果在异步回调中使用它,它将不会与在迭代其他步骤中建立的任何其他回调发生冲突。如果您碰巧在jQuery中工作,
$.each()
函数会为您提供类似的功能。经典解决方案:封闭
您要做的是将每个函数中的变量绑定(bind)到函数外部的一个不变的值:
var funcs = [];
function createfunc(i) {
return function() {
console.log("My value: " + i);
};
}
for (var i = 0; i < 3; i++) {
funcs[i] = createfunc(i);
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
由于在JavaScript中没有块作用域-只有函数作用域-通过将函数创建包装在新函数中,因此请确保“i”的值保持预期。