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的历史!现在,我们可以避免使用箭头函数的块作用域,并使用Object方法直接处理来自DOM节点的循环。

常量函数=[1,2,3].map(i=>()=>console.log(i));函数映射(fn=>fn())

const buttons=document.getElementsByTagName(“button”);对象按键(按钮).map(i=>按钮[i].addEventListener('click',()=>console.log(i)));<button>0</button><br><button>1</button><br><按钮>2</按钮>

其他回答

最初的示例不起作用的原因是,您在循环中创建的所有闭包都引用了同一帧。实际上,一个对象上有3个方法,只有一个i变量。他们都打印出相同的值。

在阅读了各种解决方案之后,我想补充一点,这些解决方案之所以有效,是因为依赖于范围链的概念。这是JavaScript在执行期间解析变量的方式。

每个函数定义形成一个范围,该范围由所有本地var声明的变量及其参数。如果我们在另一个(外部)函数中定义了内部函数形成一条链,并将在执行过程中使用当函数被执行时,运行时通过搜索作用域链来评估变量。如果在链的某个点上可以找到一个变量,它将停止搜索并使用它,否则将继续搜索,直到到达属于window的全局范围。

在初始代码中:

funcs = {};
for (var i = 0; i < 3; i++) {         
  funcs[i] = function inner() {        // function inner's scope contains nothing
    console.log("My value: " + i);    
  };
}
console.log(window.i)                  // test value 'i', print 3

当执行funcs时,作用域链将是函数inner->global。由于在函数内部找不到变量i(既没有使用var声明,也没有作为参数传递),因此它继续搜索,直到最终在全局范围(即window.i)中找到i的值。

通过将其包装在外部函数中,要么像harto那样显式定义助手函数,要么像Bjorn那样使用匿名函数:

funcs = {};
function outer(i) {              // function outer's scope contains 'i'
  return function inner() {      // function inner, closure created
   console.log("My value: " + i);
  };
}
for (var i = 0; i < 3; i++) {
  funcs[i] = outer(i);
}
console.log(window.i)          // print 3 still

当执行funcs时,现在作用域链将是函数inner->函数outer。这一次,可以在for循环中执行3次的外部函数的作用域中找到i,每次都有正确绑定的值i。当内部执行时,它不会使用window.i的值。

更多详情请点击此处它包括在循环中创建闭包的常见错误,以及我们需要闭包的原因和性能考虑。

我很惊讶还没有人建议使用forEach函数来更好地避免(重新)使用局部变量。事实上,出于这个原因,我不再使用for(var I…)。

[0,2,3].forEach(function(i){ console.log('My value:', i); });
// My value: 0
// My value: 2
// My value: 3

//编辑为使用forEach而不是map。

首先,了解此代码的错误:

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.
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

这里,当funcs[]数组初始化时,i递增,funcs数组初始化,func数组的大小变为3,因此i=3,。现在,当调用funcs[j]()时,它再次使用变量i,变量i已经增加到3。

现在要解决这个问题,我们有很多选择。以下是其中两个:

我们可以用let初始化i,或者用let初始化一个新的变量索引,并使其等于i。因此,当进行调用时,将使用索引,其范围将在初始化后结束。对于调用,索引将再次初始化:var函数=[];对于(var i=0;i<3;i++){设索引=i;funcs[i]=函数(){console.log(“我的值:”+索引);};}对于(变量j=0;j<3;j++){函数[j]();}其他选项可以是引入一个返回实际函数的tempFunc:var函数=[];函数tempFunc(i){返回函数(){console.log(“我的值:”+i);};}对于(var i=0;i<3;i++){funcs[i]=tempFunc(i);}对于(变量j=0;j<3;j++){函数[j]();}

问题是,每个匿名函数中的变量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”的值保持如预期的那样。