循环内的 JavaScript 闭包——简单实用的示例

新手上路,请多包涵
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>

… 或异步代码,例如使用 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 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();
}

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

原文由 nickf 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 795
2 个回答

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

ES6 解决方案: let

ECMAScript 6 (ES6) 引入了新的 letconst 关键字,它们的作用域不同于基于 var 的变量。例如,在具有 let 索引的循环中,循环中的每次迭代都会有一个新变量 i 具有循环范围,因此您的代码将按预期工作。有很多资源,但我推荐 2ality 的块范围帖子 作为重要的信息来源。

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

但请注意,IE9-IE11 和 Edge 14 之前的 Edge 支持 let 上述错误(它们不会每次都创建一个新的 i ,所以上面的所有函数都会像我们使用 var 一样记录 3)。 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() 函数可以为您提供类似的功能。


经典解决方案:闭包

您要做的是将每个函数内的变量绑定到函数外的一个单独的、不变的值:

 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”的值保持您的预期。

原文由 harto 发布,翻译遵循 CC BY-SA 4.0 许可协议

尝试:

 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 的回答 是现在做这种事情的最好方法。还有破折号/下划线的 _.partial 当你不需要或不想乱用 bindthisArg

原文由 Bjorn 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题