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();}
这个基本问题的解决方案是什么?
计数器是基元
让我们如下定义回调函数:
// ****************************
// 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
OP显示的代码的主要问题是,直到第二个循环才读取i。为了演示,想象一下在代码中看到一个错误
funcs[i] = function() { // and store them in funcs
throw new Error("test");
console.log("My value: " + i); // each should log its value.
};
在执行funcs[someIndex]()之前,错误实际上不会发生。使用相同的逻辑,应该很明显,直到这一点,i的值也不会被收集。一旦原始循环完成,i++将i值设为3,这导致条件i<3失败,循环结束。此时,i为3,因此当使用funcs[someIndex]()时,每次计算i时,i均为3。
要克服这一点,必须在遇到i时对其求值。注意,这已经以funcs[i]的形式发生(其中有3个唯一索引)。有几种方法可以获取此值。一种是将其作为参数传递给函数,该函数在这里已经以多种方式显示。
另一种选择是构造一个函数对象,它将能够在变量上关闭。这样就可以完成
jsFiddle演示
funcs[i] = new function() {
var closedVariable = i;
return function(){
console.log("My value: " + closedVariable);
};
};
您可以为数据列表(如查询js(*))使用声明性模块。在这些情况下,我个人认为声明性方法不太令人惊讶
var funcs = Query.range(0,3).each(function(i){
return function() {
console.log("My value: " + i);
};
});
然后,您可以使用第二个循环并获得预期的结果,也可以这样做
funcs.iterate(function(f){ f(); });
(*)我是query js的作者,因此倾向于使用它,所以不要将我的话作为仅针对声明性方法的所述库的建议:)