var函数=[];//让我们创建3个函数对于(变量i=0;i<3;i++){//并将其存储在funcs中funcs[i]=函数(){//每个都应该记录其值。console.log(“我的值:”,i);};}对于(变量j=0;j<3;j++){//现在让我们逐一查看函数[j]();}

它输出以下内容:

我的价值:3我的价值:3我的价值:3

而我希望它输出:

我的值:0我的价值:1我的价值:2


当使用事件侦听器导致函数运行延迟时,也会出现同样的问题:

var buttons=document.getElementsByTagName(“button”);//让我们创建3个函数for(var i=0;i<buttons.length;i++){//作为事件侦听器buttons[i].addEventListener(“单击”,函数(){//每个都应该记录其值。console.log(“我的值:”,i);});}<按钮>0</按钮><br/><button>1</button><br/><按钮>2</按钮>

…或异步代码,例如使用Promises:

//一些异步等待函数const wait=(ms)=>new Promise((解析,拒绝)=>setTimeout(解析,ms));对于(变量i=0;i<3;i++){//一旦每个承诺得到解决,就记录“i”。等待(i*100)。然后(()=>console.log(i));}

在for in和for of循环中也很明显:

常量arr=[1,2,3];常量fns=[];用于(arr中的变量i){fns.push(()=>console.log(“index:”,i));}对于(arr的var v){fns.push(()=>console.log(“值:”,v));}for(arr常量){var obj={number:n};//或新的MyLibObject({…})fns.push(()=>console.log(“n:”,n,“|”,“obj:”,JSON.stringify(obj)));}for(fns的var f){f();}

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


当前回答

JavaScript函数在声明时“关闭”它们有权访问的作用域,并保留对该作用域的访问权,即使该作用域中的变量发生变化。

var函数=[]对于(变量i=0;i<3;i+=1){funcs[i]=函数(){控制台日志(i)}}对于(var k=0;k<3;k+=1){函数[k]()}

上面数组中的每个函数都在全局范围(全局,因为这恰好是它们声明的范围)上关闭。

稍后调用这些函数,记录全局范围中i的最新值。这就是结束的魔力和挫折。

“JavaScript函数在声明它们的作用域上关闭,并保留对该作用域的访问权,即使该作用域内的变量值发生变化。”

使用let而不是var可以解决这个问题,方法是在每次for循环运行时创建一个新的作用域,为每个要关闭的函数创建一个单独的作用域。其他各种技术对额外的功能也有同样的作用。

var函数=[]for(设i=0;i<3;i+=1){funcs[i]=函数(){控制台日志(i)}}对于(var k=0;k<3;k+=1){函数[k]()}

(让变量成为块范围。块用大括号表示,但在for循环的情况下,初始化变量i在我们的情况下被认为是在大括号中声明的。)

其他回答

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

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

Try:

var函数=[];对于(变量i=0;i<3;i++){funcs[i]=(函数(索引){返回函数(){console.log(“我的值:”+索引);};}(i) );}对于(变量j=0;j<3;j++){函数[j]();}

编辑(2014):

就我个人而言,@Aust最近关于使用.bind的回答是目前最好的解决方法。当您不需要或不想使用bind的thisArg时,也可以使用破折号/下划线的_.partial。

你的代码不起作用,因为它的作用是:

Create variable `funcs` and assign it an empty array;  
Loop from 0 up until it is less than 3 and assign it to variable `i`;
    Push to variable `funcs` next function:  
        // Only push (save), but don't execute
        **Write to console current value of variable `i`;**

// First loop has ended, i = 3;

Loop from 0 up until it is less than 3 and assign it to variable `j`;
    Call `j`-th function from variable `funcs`:  
        **Write to console current value of variable `i`;**  
        // Ask yourself NOW! What is the value of i?

现在问题是,调用函数时变量i的值是多少?因为第一个循环是在i<3的条件下创建的,所以当条件为false时,它会立即停止,所以它是i=3。

您需要理解,当您的函数被创建时,它们的代码都不会被执行,只会被保存以备以后使用。因此,当稍后调用它们时,解释器会执行它们并问:“i的当前值是多少?”

因此,您的目标是首先将i的值保存到函数,然后才将函数保存到funcs。例如,可以这样做:

var funcs = [];
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function(x) {            // and store them in funcs
        console.log("My value: " + x); // each should log its value.
    }.bind(null, i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

这样,每个函数都有自己的变量x,我们在每次迭代中将这个x设置为i的值。

这只是解决这个问题的多种方法之一。

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

ES6解决方案:让

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

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

不过,请注意,IE9-IE11和Edge 14之前的Edge支持以上错误(它们不会每次都创建一个新的i,所以上面的所有函数都会记录3,就像我们使用var时一样)。边缘14最终得到了正确的结果。


ES5.1解决方案:针对每个

随着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函数=[];函数createfunc(i){返回函数(){console.log(“我的值:”+i);};}对于(变量i=0;i<3;i++){funcs[i]=创建函数(i);}对于(变量j=0;j<3;j++){//现在让我们逐一查看函数[j]();}

由于JavaScript中没有块作用域-只有函数作用域-通过将函数创建包装在一个新函数中,可以确保“i”的值保持如预期的那样。

另一种尚未提及的方法是使用Function.prototype.bind

var funcs={};对于(变量i=0;i<3;i++){funcs[i]=函数(x){console.log('我的值:'+x);}.绑定(this,i);}对于(变量j=0;j<3;j++){函数[j]();}

更新

正如@squit和@mekdev所指出的,通过首先在循环外创建函数,然后在循环内绑定结果,可以获得更好的性能。

函数日志(x){console.log('我的值:'+x);}var函数=[];对于(变量i=0;i<3;i++){funcs[i]=log.bind(this,i);}对于(变量j=0;j<3;j++){函数[j]();}