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]();
}
它的输出结果是这样的。
我的值。3 我的值。3 我的价值3
而我希望它能输出。
我的值:0 我的值:1 我的值:2
当运行函数的延迟是由使用事件监听器造成的,也会出现同样的问题。
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>
// 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));
}
这个基本问题的解决方法是什么?
好吧,问题是在你的每个匿名函数中的变量i
,都与函数外的同一个变量绑定。
你要做的是将每个函数中的变量绑定到函数外的一个单独的、不变的值。
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"的值保持你的意图。
随着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()
函数给你一个类似的能力。
let
。ECMAScript 6 (ES6)引入了新的let
和const
关键字,它们的作用域与基于var
的变量不同。例如,在一个具有基于let
的索引的循环中,每一次通过循环的迭代都会有一个i
的新值,其中每个值都是在循环中的作用域,所以你的代码会按照你的期望工作。有很多资源,但我推荐[2ality'的block-scoping帖子](http://www.2ality.com/2015/02/es6-scoping.html)作为一个很好的信息来源。
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
但请注意,IE9-IE11和Edge 14之前的版本支持 "let",但对上述情况的处理是错误的(它们不会每次都创建一个新的 "i",所以上述所有函数都会像我们使用 "var "那样记录3次)。Edge 14终于把它弄对了。
试试吧。
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = (function(index) {
return function() {
console.log("My value: " + index);
};
}(i));
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
编辑(2014年)。
我个人认为@Aust'的关于使用.bind
的最新答案是现在做这种事的最好方法。还有lo-dash/underscore'的_.partial
,当你不需要或不想搞乱bind
'的thisArg
时。