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

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


当前回答

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

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的值。

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

其他回答

这是该技术的另一种变体,类似于Bjorn(apphacker),它允许您在函数内分配变量值,而不是将其作为参数传递,这有时可能更清楚:

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

请注意,无论使用何种技术,索引变量都会成为一种静态变量,绑定到内部函数的返回副本。即,在调用之间保留对其值的更改。它可以非常方便。

这是异步代码中经常遇到的问题,变量i是可变的,在调用函数时,使用i的代码将被执行,并且i将变为其最后一个值,因此意味着在循环中创建的所有函数都将创建一个闭包,i将等于3(for循环的上限+1)。

解决此问题的一个方法是创建一个函数,该函数将为每次迭代保存i的值,并强制复制i(因为它是一个原语,如果有帮助,请将其视为快照)。

随着ES6现在得到广泛支持,这个问题的最佳答案已经改变。ES6为这种情况提供了let和const关键字。我们可以使用let来设置一个循环范围变量,而不是使用闭包:

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

然后,val将指向一个特定于循环特定转弯的对象,并将返回正确的值,而无需附加闭包符号。这显然大大简化了这个问题。

const类似于let,但有一个额外的限制,即变量名不能在初始赋值后反弹到新引用。

现在,针对最新版本浏览器的浏览器提供了浏览器支持。const/let目前在最新的Firefox、Safari、Edge和Chrome中受支持。Node也支持它,您可以利用Babel等构建工具在任何地方使用它。您可以在这里看到一个工作示例:http://jsfiddle.net/ben336/rbU4t/2/

此处的文档:

常量允许

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

这证明了javascript在“闭包”和“非闭包”的工作方式方面是多么丑陋。

在以下情况下:

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]是一个全局函数,并且'console.log(“我的值:”+i);'正在打印全局变量i

假使

var funcs = [];

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

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

由于javascript的这种扭曲的闭包设计,'console.log(“我的值:”+i);'正在从外部函数“createfunc(i)”打印i

这一切都是因为javascript无法像C编程语言那样在函数中设计出像“静态”变量这样的好东西!

许多解决方案看起来都是正确的,但他们没有提到它叫做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);
  }
}

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