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

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


当前回答

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

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

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

其他回答

最初的示例不起作用的原因是,您在循环中创建的所有闭包都引用了同一帧。实际上,一个对象上有3个方法,只有一个i变量。他们都打印出相同的值。

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

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

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

使用let(阻塞范围)代替var。

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

这是异步代码中经常遇到的问题,变量i是可变的,在调用函数时,使用i的代码将被执行,并且i将变为其最后一个值,因此意味着在循环中创建的所有函数都将创建一个闭包,i将等于3(for循环的上限+1)。

解决此问题的一个方法是创建一个函数,该函数将为每次迭代保存i的值,并强制复制i(因为它是一个原语,如果有帮助,请将其视为快照)。

计数器是基元

让我们如下定义回调函数:

// ****************************
// COUNTER BEING A PRIMITIVE
// ****************************
function test1() {
    for (var i=0; i<2; i++) {
        setTimeout(function() {
            console.log(i);
        });
    }
}
test1();
// 2
// 2

超时完成后,它将为两个打印2。这是因为回调函数访问基于词法范围的值,该范围是函数定义的地方。

为了在定义回调时传递和保留值,我们可以创建一个闭包,在调用回调之前保留值。这可以通过以下方式完成:

function test2() {
    function sendRequest(i) {
        setTimeout(function() {
            console.log(i);
        });
    }

    for (var i = 0; i < 2; i++) {
        sendRequest(i);
    }
}
test2();
// 1
// 2

现在,这一点的特殊之处在于“基元是按值传递并复制的。因此,当定义闭包时,它们会保留上一个循环的值。”

计数器是一个对象

由于闭包可以通过引用访问父函数变量,因此这种方法与原语不同。

// ****************************
// COUNTER BEING AN OBJECT
// ****************************
function test3() {
    var index = { i: 0 };
    for (index.i=0; index.i<2; index.i++) {
        setTimeout(function() {
            console.log('test3: ' + index.i);
        });
    }
}
test3();
// 2
// 2

因此,即使为作为对象传递的变量创建了闭包,也不会保留循环索引的值。这是为了表明对象的值不是复制的,而是通过引用访问的。

function test4() {
    var index = { i: 0 };
    function sendRequest(index, i) {
        setTimeout(function() {
            console.log('index: ' + index);
            console.log('i: ' + i);
            console.log(index[i]);
        });
    }

    for (index.i=0; index.i<2; index.i++) {
        sendRequest(index, index.i);
    }
}
test4();
// index: { i: 2}
// 0
// undefined

// index: { i: 2}
// 1
// undefined