javascript - javascript关闭内部循环- - 简单实用的示例

  显示原文与译文双语对照的内容

闭包是一个一直在讨论的很多的东西,但这种情况出现很多对我来说,我总是抓我的头。


var funcs = {};
for (var i = 0; i <3; i++) {//let's create 3 functions
 funcs[i] = function() {//and store them in funcs
 console.log("My value:" + i);//each should log its value.
 };
}
for (var j = 0; j <3; j++) {
 funcs[j]();//and now let's run each one to see
}

它输出:

我的值:3
我的值:3
我的值:3

而我希望它输出:

我的值:0
我的值:1
我的值:2

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

时间:

问题是,在你的匿名函数中,变量i 被绑定到函数之外的同一个变量。

你要做的是将每个函数中的变量绑定到函数之外的一个单独的不变值:


var funcs = [];

function createfunc(i) {
 return function() { console.log("My value:" + i); };
}

for (var i = 0; i <3; i++) {
 funcs[i] = createfunc(i);
}

for (var j = 0; j <3; j++) {
 funcs[j]();//and now let's run each one to see
}

由于JavaScript中没有块作用域- 只有函数作用域- 通过将函数创建包装在一个新函数中,你可以确保"我"的值保持在你想要的。


更新:相对普及的( 在 2015中) Array.prototype.forEach 功能,值得注意的是,在这种情况下主要涉及迭代数组的值, .forEach() 提供了一个干净、自然的方式为每次迭代得到一个不同的闭包。 也就是说,假设你有一些包含值( DOM引用,对象,任何)的数组,并且在设置特定于每个元素的回调时出现问题,你可以这样做:


var someArray = [/* whatever */];
//...
someArray.forEach(function(arrayElement) {
//... code code code for this one element
 someAsynchronousFunction(arrayElement, function() {
 arrayElement.doSomething();
 });
});

这个想法是每次调用 .forEach 循环的回调函数都是它自己的闭包。 传递给该处理程序的参数是特定于迭代特定步骤的数组元素。 如果在异步回调中使用,它将不会与在迭代的其他步骤中建立的任何其他回调冲突。

如果你碰巧使用 jQuery,$.each() 函数会给你一个类似的功能。

的更新 2: 6 ( ES6 ),最新版本的JavaScript,现在开始在许多长青浏览器和后端系统中实现。 还有transpilers像巴贝尔将转换ES6es5的新特性,允许使用老系统。

ES6引入了新的letconst 关键字,它们的作用域与 var -based变量不同。 例如在带有 let -based索引的循环中,通过循环的每次迭代都有一个新的值 i,其中每个值都在循环内,因此你的代码将按预期的方式工作。 有很多资源,但我推荐 block-scoping 2ality post 作为一个重要的信息来源。


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

尝试以下方法:


var funcs = [];

for (var i = 0; i <3; i++) {
 funcs[i] = (function(index) {
 return function() {
 console.log("My value:" + index);
 };
 }(i));
}
for (var j = 0; j <3; j++) {
 funcs[j]();
}

编辑 ( 2014 ):

我个人认为 @Aust's 关于使用 .bind的最新答案是现在做这种事情的最好方法。 There is also _.partial of lo-dash/underscore when you do not need or want to mess with bindthisArg of.

还没有提及的另一种方法是使用 Function.prototype.bind


var funcs = {};
for (var i = 0; i <3; i++) {
 funcs[i] = function(x) {
 console.log('My value: ' + x);
 }.bind(this, i);
}
for (var j = 0; j <3; j++) {
 funcs[j]();
}

jsFiddle

使用 Immediately-Invoked函数表达式life jQuery文档,最简单和最易读的方法来封装索引变量:


for (var i = 0; i <3; i++) {

 (function(index) {
 console.log('iterator: ' + index);
//now you can also loop an ajax call here without problems: $.ajax({});
 })(i);

}

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

你需要理解的是javascript中变量的作用域基于函数。 这是一个重要的区别比 C# 有块范围,并将变量复制到一个内部的工作。

将它包装在一个函数中,计算返回函数的函数就像apphacker的答案一样,因为变量现在具有函数作用域。

还有一个允许使用块作用域规则的执勤关键字,而不是 var 。 在这种情况下,在中定义一个变量就可以。 也就是说,由于兼容性,执勤keyword关键字不是一个实际的解决方案。


var funcs = {};
for (var i = 0; i <3; i++) {
 let index = i;//add this
 funcs[i] = function() { 
 console.log("My value:" + index);//change to the copy
 };
}
for (var j = 0; j <3; j++) {
 funcs[j](); 
}

这描述了在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 副本来引用新对象。 因此,counter1counter2 彼此独立。

循环中的闭合

在循环中使用闭包很棘手。

请考虑:


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

请注意,counters[0]counters[1] 不是独立的 。 事实上,他们在同一个 obj 上操作 !

这是因为在循环的所有迭代中只有一个 obj 共享,可能是出于性能原因。 即使 {counter: 0} 在每次迭代中创建一个新的对象,同样的obj 副本也会得到更新,并引用最新的对象。

解决方案是使用另一个 helper 函数:


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

这是因为函数作用域中的局部变量,以及函数变量变量在输入时被分配新的副本。

有关详细讨论,请参见 JavaScript关闭陷阱和用法

这是另一种变异的技术,类似于( apphacker ) Bjorn,它可以让你指定函数内的变量值,而不是将它作为参数传递,有时可能是清晰的:


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

请注意,无论使用何种技术,index 变量都成为一个静态变量,绑定到内部函数的返回副本。 换句话说,对它的值的更改在调用之间保留。 它很方便。

最简单的解决方案是

而不是使用这个


var funcs = [];
for(var i =0; i<3; i++){
 funcs[i] = function(){
 alert(i);
 }
}

for(var j =0; j<3; j++){
 funcs[j]();
}

通知 2,3次。 使用这个


var funcs = [];
for(var new_i =0; new_i<3; new_i++){
 (function(i){
 funcs[i] = function(){
 alert(i);
 }
 })(new_i);
}

for(var j =0; j<3; j++){
 funcs[j]();
}

它的背后的思想是,用一个自调用匿名函数封装整个循环的整个主体,并将"new_i"作为参数传递并将它的捕获为"我"。 由于匿名函数立即执行,所以在匿名函数中定义的每个函数的"我"值都不同。 这个解决方案似乎适合任何这样的问题,因为它需要对这个问题的原始代码进行最小改动。 实际上,这是设计,它根本不是一个问题 !

...