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

3229

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 (const n of arr) {
  var obj = { number: n }; // or new MyLibObject({ ... })
  fns.push(() => console.log("n:", n, "|", "obj:", JSON.stringify(obj)));
}

for(var f of fns){
  f();
}

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


59
在ES6中,一个简单的解决方案是使用let声明变量_i,它具有循环体作用域。 - Tomas Nikodym
4
JS函数在声明时“闭合”了它们可以访问的作用域,并保留对该作用域的访问权限,即使该作用域中的变量发生变化。上述数组中的每个函数都关闭了全局作用域(全局作用域只是因为它们恰好是在其中声明的作用域)。稍后,这些函数被调用以记录全局作用域中“i”的最新值。这就是JS :) 使用let代替var通过在每次循环运行时创建一个新的作用域,为每个函数创建单独的作用域来解决此问题。其他各种技术使用额外的函数完成相同的操作。 - Costa Michailidis
45个回答

0

让我们利用{{link1:new Function}}。因此,i不再是闭包的变量,而只是文本的一部分:

var funcs = [];
for (var i = 0; i < 3; i++) {
    var functionBody = 'console.log("My value: ' + i + '");';
    funcs[i] = new Function(functionBody);
}

for (var j = 0; j < 3; j++) {
    funcs[j]();
}

3
这很慢,可能存在安全风险,并且不适用于所有情况。 - lovasoa

-1
  asyncIterable = [1,2,3,4,5,6,7,8];

  (async function() {
       for await (let num of asyncIterable) {
         console.log(num);
       }
    })();

最好在代码中添加一些解释,以便用户更好地理解它。尝试解释为什么你的方法比其他答案更好 :) - Federico Grandi
感谢@FedericoGrandi,在看了两页的示例后,我认为这不是必需的。 - swogger

-2
为什么不直接在创建完函数后,在第一个(也是唯一的)循环中立即调用每个函数呢?例如:
 var funcs = [];
    for (var i = 0; i < 3; i++) {
    // let's create 3 functions
    funcs[i] = function() {
    // and store them in funcs
    console.log("My value: " + i); // each should log its value.
    };
    funcs[i]();// and now let's run each one to see
    }

2
因为这只是问题的一个示例。 - nickf

-2

ES6 的支持下,最好的方法是使用 letconst 关键字来处理这种情况。因此,var 变量会被 hoisted,并且随着循环结束,i 的值会更新为所有 closures 的值...,我们可以像这样使用 let 来设置循环范围变量:

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

-2

这个问题已经有很多有效的答案了。但是没有多少人使用函数式方法。这里提供一种替代方案,使用forEach方法,它可以很好地与回调和闭包一起使用:

let arr = [1,2,3];

let myFunc = (val, index) => { 
    console.log('val: '+val+'\nindex: '+index);
};

arr.forEach(myFunc);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接