var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = function() {
console.log(i);
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
如上,输出的结果是3个“3”,但是想输出的是“0 1 2”。
当使用时间监听器导致函数运行延迟时,也出现同样的问题:
var buttons = document.getElementsByTagName("button");
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", function() {
console.log(i);
});
}
或异步代码:
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));
for(var i = 0; i < 3; i++){
wait(i * 100).then(() => console.log(i));
}
这个问题的解决办法是什么?
问题在于变量i,在每个匿名函数中,都被绑定到函数外的同一个变量上。
经典解决方案:闭包
你要做的就是将每个函数中的变量绑定到函数外部一个单独的,不变的值:
var funcs = [];
function createfunc(i) {
return function() { console.log(i); };
}
for (var i = 0; i < 3; i++) {
funcs[i] = createfunc(i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
由于JavaScript中没有块作用域,只有函数作用域,通过将函数创建在一个新函数中,你可以确保i的值保持为你想要的值。
JS2015解决方案:forEach
随着相对广泛的可用性数组,原型,值得注意的是,在那些设计值数组迭代的情况下,forEach提供了一种干净、自然的方法来每个迭代获得不同的闭包。也就说,假设你有某种包含值的数组(DOM引用,对象等),并且出现了针对每个元素设置回调的问题,你可以这样做:
var arrays = [ /*values*/ ];
arrays.forEach(function(arrayElement) {
someAsynchronousFunction(arrayElement, function() {
arrayElement.doSomething();
});
});
其思想是,forEach循环使用的回调函数的每次调用都是它自己的闭包,传入该处理程序的参数是特定于迭代的特定步骤的数组元素。如果在异步回调中使用它,它将不会与在迭代的其他步骤中建立的任何其他回调发生冲突。如果你碰巧使用jQuery, $.each()函数提供了类似的功能。
ES6解决方案:let
ECMAScript 6 (ES6)引入了新的let和const关键字,它们的作用域不同于基于var的变量。例如,在一个基于let的索引的循环中,循环中的每个迭代都有一个新值i,其中每个值都在循环的作用域内,因此你的代码将如你所期望的那样工作。
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log(i);
};
}
评论前必须登录!
注册