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

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


当前回答

最简单的解决方案是,

而不是使用:

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

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

警报“2”3次。这是因为在for循环中创建的匿名函数共享相同的闭包,并且在该闭包中,i的值是相同的。使用此选项可防止共享关闭:

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

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

这背后的想法是,用IIFE(立即调用函数表达式)封装for循环的整个主体,并将new_i作为参数传递,并将其捕获为i。由于匿名函数是立即执行的,因此匿名函数内部定义的每个函数的i值都不同。

此解决方案似乎适合任何此类问题,因为它需要对受此问题困扰的原始代码进行最小的更改。事实上,这是故意的,根本不应该是一个问题!

其他回答

您需要了解的是javascript中变量的范围是基于函数的。这与c#有一个重要的区别,因为c#有块作用域,只需将变量复制到for中的一个即可。

将其包装在一个函数中,该函数会像apphacker的答案那样计算返回函数的值,这会起到作用,因为变量现在具有函数作用域。

还有一个let关键字而不是var,这将允许使用块范围规则。在这种情况下,在for中定义一个变量就可以了。也就是说,由于兼容性,let关键字不是一个实用的解决方案。

var funcs={};对于(变量i=0;i<3;i++){设索引=i//添加此funcs[i]=函数(){console.log(“我的值:”+索引)//更改副本};}对于(变量j=0;j<3;j++){函数[j]();}

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在我们的情况下被认为是在大括号中声明的。)

直到ES5,这个问题只能用闭包来解决。

但现在在ES6中,我们有块级范围变量。将var更改为let in first for循环将解决问题。

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

我很惊讶还没有人建议使用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++) {     
  funcs[i] = function() {          
    console.log("My value: " + i); 
 };
}

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

变化很小,几乎很难看出我做了什么。我将第二个迭代器从j切换到I。这会及时刷新I的状态,从而获得所需的结果。我这样做是偶然的,但考虑到以前的答案,这是有道理的。

我写这篇文章是为了指出这一微小但非常重要的区别。希望这有助于为像我这样的其他学习者澄清一些困惑。

注:我没有分享这个,因为我认为这是正确的答案。这是一个在某些情况下可能会破裂的解决方案。事实上,我很惊讶它真的有效。