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

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


当前回答

我很惊讶还没有人建议使用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。

其他回答

下面是一个使用forEach的简单解决方案(可追溯到IE9):

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

打印:

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

Try:

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

编辑(2014):

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

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

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

这描述了在JavaScript中使用闭包的常见错误。

函数定义新环境

考虑:

function makeCounter()
{
  var obj = {counter: 0};
  return {
    inc: function(){obj.counter ++;},
    get: function(){return obj.counter;}
  };
}

counter1 = makeCounter();
counter2 = makeCounter();

counter1.inc();

alert(counter1.get()); // returns 1
alert(counter2.get()); // returns 0

每次调用makeCounter时,{counter:0}都会创建一个新对象。另外,obj的新副本也被创建以引用新对象。因此,计数器1和计数器2彼此独立。

回路中的闭合

在循环中使用闭包很棘手。

考虑:

var counters = [];

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = {
      inc: function(){obj.counter++;},
      get: function(){return obj.counter;}
    }; 
  }
}

makeCounters(2);

counters[0].inc();

alert(counters[0].get()); // returns 1
alert(counters[1].get()); // returns 1

请注意,计数器[0]和计数器[1]不是独立的。事实上,它们对同一个对象进行操作!

这是因为在循环的所有迭代中只有一个obj副本共享,可能是出于性能原因。即使{counter:0}在每次迭代中都创建了一个新对象,obj的同一副本也只会用对最新对象的引用。

解决方案是使用另一个助手函数:

function makeHelper(obj)
{
  return {
    inc: function(){obj.counter++;},
    get: function(){return obj.counter;}
  }; 
}

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = makeHelper(obj);
  }
}

这是因为直接分配了函数范围中的局部变量以及函数参数变量新副本。

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

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

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