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();}
这个基本问题的解决方案是什么?
您可以为数据列表(如查询js(*))使用声明性模块。在这些情况下,我个人认为声明性方法不太令人惊讶
var funcs = Query.range(0,3).each(function(i){
return function() {
console.log("My value: " + i);
};
});
然后,您可以使用第二个循环并获得预期的结果,也可以这样做
funcs.iterate(function(f){ f(); });
(*)我是query js的作者,因此倾向于使用它,所以不要将我的话作为仅针对声明性方法的所述库的建议:)
在阅读了各种解决方案之后,我想补充一点,这些解决方案之所以有效,是因为依赖于范围链的概念。这是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的值。
更多详情请点击此处它包括在循环中创建闭包的常见错误,以及我们需要闭包的原因和性能考虑。