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

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


当前回答

如果您在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++;}

其他回答

  asyncIterable = [1,2,3,4,5,6,7,8];

  (async function() {
       for await (let num of asyncIterable) {
         console.log(num);
       }
    })();

使用立即调用函数表达式,这是封闭索引变量的最简单和最易读的方法:

对于(变量i=0;i<3;i++){(函数(索引){console.log('iterator:'+索引);//现在您还可以在这里循环ajax调用//而不会丢失迭代器值:$.ajax({});})(i) ;}

这将迭代器i发送到我们定义为索引的匿名函数中。这将创建一个闭包,其中变量i将被保存以供以后在IIFE中的任何异步功能中使用。

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

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

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

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

在阅读了各种解决方案之后,我想补充一点,这些解决方案之所以有效,是因为依赖于范围链的概念。这是JavaScript在执行期间解析变量的方式。

每个函数定义形成一个范围,该范围由所有本地var声明的变量及其参数。如果我们在另一个(外部)函数中定义了内部函数形成一条链,并将在执行过程中使用当函数被执行时,运行时通过搜索作用域链来评估变量。如果在链的某个点上可以找到一个变量,它将停止搜索并使用它,否则将继续搜索,直到到达属于window的全局范围。

在初始代码中:

funcs = {};
for (var i = 0; i < 3; i++) {         
  funcs[i] = function inner() {        // function inner's scope contains nothing
    console.log("My value: " + i);    
  };
}
console.log(window.i)                  // test value 'i', print 3

当执行funcs时,作用域链将是函数inner->global。由于在函数内部找不到变量i(既没有使用var声明,也没有作为参数传递),因此它继续搜索,直到最终在全局范围(即window.i)中找到i的值。

通过将其包装在外部函数中,要么像harto那样显式定义助手函数,要么像Bjorn那样使用匿名函数:

funcs = {};
function outer(i) {              // function outer's scope contains 'i'
  return function inner() {      // function inner, closure created
   console.log("My value: " + i);
  };
}
for (var i = 0; i < 3; i++) {
  funcs[i] = outer(i);
}
console.log(window.i)          // print 3 still

当执行funcs时,现在作用域链将是函数inner->函数outer。这一次,可以在for循环中执行3次的外部函数的作用域中找到i,每次都有正确绑定的值i。当内部执行时,它不会使用window.i的值。

更多详情请点击此处它包括在循环中创建闭包的常见错误,以及我们需要闭包的原因和性能考虑。

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

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