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并让逐一地。

案例1:使用var

<script>
   var funcs = [];
   for (var i = 0; i < 3; i++) {
     funcs[i] = function () {
        debugger;
        console.log("My value: " + i);
     };
   }
   console.log(funcs);
</script>

现在,按F12打开铬控制台窗口并刷新页面。在数组中每3个函数展开一次。您将看到一个名为[[Scopes]]的属性。展开该属性。你会看到一个名为“Global”的数组对象,展开该对象。您将发现对象中声明的属性“i”具有值3。

结论:

当您在函数外部使用“var”声明变量时,它将变为全局变量(您可以通过键入i或window.i。它将返回3)。除非调用功能。调用函数时,console.log(“My value:”+i)从其Global对象中获取值,并显示后果

CASE2:使用let

现在将“var”替换为“let”

<script>
    var funcs = [];
    for (let i = 0; i < 3; i++) {
        funcs[i] = function () {
           debugger;
           console.log("My value: " + i);
        };
    }
    console.log(funcs);
</script>

做同样的事情,去观察镜。现在您将看到两个对象“块”和“全局”。现在展开Block对象将看到这里定义了“i”,奇怪的是,对于每个函数,如果i不同(0,1,2),则其值是不同的。

结论:

当您在函数外部但在循环内部使用“let”声明变量时,此变量将不是全局变量变量,它将成为块级变量,仅可用于同一函数。这就是为什么,我们调用函数时,每个函数的i值都不同。

有关更紧密的工作原理的更多信息,请浏览精彩的视频教程https://youtu.be/71AtaJpJHw0

其他回答

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

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

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

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

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

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

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

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

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

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

下面是一个使用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

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

设arr=[1,2,3];

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

arr.forEach(myFunc);