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

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


当前回答

许多解决方案看起来都是正确的,但他们没有提到它叫做Currying,这是一种针对类似情况的函数式编程设计模式。根据浏览器的不同,比绑定快3-10倍。

var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = curryShowValue(i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

function curryShowValue(i) {
  return function showValue() {
    console.log("My value: " + i);
  }
}

查看不同浏览器中的性能增益。

其他回答

另一种说法是,函数中的i在执行函数时绑定,而不是在创建函数时绑定。

当您创建闭包时,i是对外部范围中定义的变量的引用,而不是创建闭包时的副本。将在执行时进行评估。

大多数其他答案提供了通过创建另一个不会改变值的变量来解决问题的方法。

我只是想给你解释清楚。就我个人而言,为了解决这个问题,我会选择Harto的方法,因为从这里的答案来看,这是最不言自明的方法。发布的任何代码都可以使用,但我宁愿选择闭包工厂,而不必写一堆注释来解释为什么我要声明一个新变量(Freddy和1800)或具有奇怪的嵌入闭包语法(apphacker)。

让我们利用新功能。因此,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]();
}

如果您在while循环而不是for循环中遇到此类问题,例如:

变量i=0;而(i<5){setTimeout(函数){控制台日志(i);},i*1000);i++;}

关闭当前值的技术有点不同。在while块内声明一个带有const的块范围变量,并将当前i赋给它。然后,无论在何处异步使用该变量,都用新的块范围的变量替换i:

变量i=0;而(i<5){常量this迭代i=i;setTimeout(函数){console.log(thisIterationI);},i*1000);i++;}

对于不支持块作用域变量的旧浏览器,可以使用IIFE,调用i:

变量i=0;而(i<5){(函数(innerI){setTimeout(函数){console.log(innerI);},innerI*1000);})(i) ;i++;}

如果要调用的异步操作恰好是如上所述的setTimeout,则还可以使用第三个参数调用setTimeout以指示调用传递函数的参数:

变量i=0;而(i<5){setTimeout(设置超时)((thisIterationI)=>{//回调console.log(thisIterationI);},i*1000,//延迟i//获取传递给回调函数的值;成为此迭代I);i++;}

在ES6的支持下,最好的方法是在这种情况下使用let和const关键字。所以var变量被提升,随着循环结束,所有闭包的i值被更新。。。,我们可以使用let设置一个循环范围变量,如下所示:

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

使用let(阻塞范围)代替var。

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