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](); }
它输出这个:
My value: 3 My value: 3 My value: 3
而我希望它输出:
My value: 0 My value: 1 My value: 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>
… 或异步代码,例如使用 Promises:
// 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循环中也很明显:
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每个匿名函数中的变量都绑定到函数外部的同一个变量。
i
let
ECMAScript 6 (ES6) 引入了新的let和const关键字,它们的作用域与var基于 - 的变量不同。例如,在具有let-based 索引的循环中,循环中的每次迭代都会有一个具有循环范围的新变量i,因此您的代码将按预期工作。有很多资源,但我建议2ality 的块范围帖子作为一个很好的信息来源。
const
var
for (let i = 0; i < 3; i++) { funcs[i] = function() { console.log("My value: " + i); }; }
但请注意,IE9-IE11 和 Edge 14 之前的 Edge 支持let但会出现上述错误(它们不会i每次都创建新函数,因此上述所有函数都会像我们使用 3 一样记录 3 var)。Edge 14 终于做对了。
随着Array.prototype.forEach函数相对广泛的可用性(在 2015 年),值得注意的是,在那些主要涉及对一组值进行迭代的情况下,.forEach()它提供了一种干净、自然的方式来为每次迭代获得不同的闭包。也就是说,假设您有某种包含值(DOM 引用、对象等)的数组,并且出现了为每个元素设置特定回调的问题,您可以这样做:
Array.prototype.forEach
.forEach()
var someArray = [ /* whatever */ ]; // ... someArray.forEach(function(arrayElement) { // ... code code code for this one element someAsynchronousFunction(arrayElement, function() { arrayElement.doSomething(); }); });
这个想法是,与循环一起使用的回调函数的每次调用都.forEach将是它自己的闭包。传递给该处理程序的参数是特定于迭代的特定步骤的数组元素。如果在异步回调中使用它,它不会与在迭代的其他步骤中建立的任何其他回调发生冲突。
.forEach
如果你碰巧在 jQuery 中工作,这个$.each()函数会给你类似的能力。
$.each()
您要做的是将每个函数内的变量绑定到函数外的一个单独的、不变的值:
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” 的值保持您的预期。