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</按钮>

其他回答

这描述了在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);
  }
}

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

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

这个问题已经有很多有效答案了。但使用功能方法的并不多。这里有一个使用forEach方法的替代解决方案,它可以很好地处理回调和闭包:

设arr=[1,2,3];

让myFunc=(val,index)=>{console.log('val:'+val+'\nindex:'+index);};

arr.forEach(myFunc);

Try:

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

编辑(2014):

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

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