循环中的 JavaScript 闭包 – 简单的实际示例ES6 解决方案:let ES5.1 解决方案:forEach经典解决方案:瓶盖

2022-08-29 21:47:47

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));
}

它在循环中也很明显:for infor 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();
}

这个基本问题的解决方案是什么?


答案 1

好吧,问题是,在每个匿名函数中,变量都绑定到函数外部的同一变量。i

ES6 解决方案:let

ECMAScript 6 (ES6) 引入了新的关键字和关键字,这些关键字的作用域与基于变量的作用域不同。例如,在具有基于索引的循环中,循环的每次迭代都将具有一个具有循环作用域的新变量,因此您的代码将按预期工作。有很多资源,但我建议2ality的块范围帖子作为一个很好的信息来源。letconstvarleti

for (let i = 0; i < 3; i++) {
  funcs[i] = function() {
    console.log("My value: " + i);
  };
}

但是,请注意,在Edge 14之前支持IE9-IE11和Edge,但是上述错误(它们不会每次都创建一个新的,因此上面的所有函数都会像我们使用时一样记录3)。Edge 14终于做对了。letivar


ES5.1 解决方案:forEach

随着该函数的相对广泛可用性(在2015年),值得注意的是,在那些主要涉及对一组值进行迭代的情况下,提供了一种干净,自然的方式来为每次迭代获得不同的闭包。也就是说,假设您有某种包含值(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

如果您碰巧在jQuery中工作,则该函数将为您提供类似的功能。$.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”的值保持预期。


答案 2

尝试:

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/下划线,当你不需要或不想弄乱 ' s ._.partialbindthisArg