关于循环:javascript不支持带有局部变量的闭包吗?

Doesn't JavaScript support closures with local variables?

本问题已经有最佳答案,请猛点这里访问。

我对这个代码感到非常困惑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var closures = [];
function create() {
  for (var i = 0; i < 5; i++) {
    closures[i] = function() {
      alert("i =" + i);
    };
  }
}

function run() {
  for (var i = 0; i < 5; i++) {
    closures[i]();
  }
}

create();
run();

根据我的理解,它应该打印0、1、2、3、4(这不是闭包的概念吗?).

而是打印5、5、5、5、5。

我试过犀牛和火狐。

有人能给我解释一下这种行为吗?提前通知。


通过添加额外的匿名函数修复了jon的答案:

1
2
3
4
5
6
7
8
9
function create() {
  for (var i = 0; i < 5; i++) {
    closures[i] = (function(tmp) {
        return function() {
          alert("i =" + tmp);
        };
    })(i);
  }
}

解释是,javascript的作用域是函数级的,而不是块级的,创建一个闭包仅仅意味着将封闭作用域添加到封闭函数的词汇环境中。

循环终止后,函数级变量i的值为5,这就是内部函数"看到的"。

附带说明:您应该注意不必要的函数对象创建,尤其是在循环中;它效率很低,如果涉及到DOM对象,很容易创建循环引用,从而在Internet Explorer中引入内存泄漏。


解决方案是让一个自执行lambda包装数组push。你也把i作为参数传递给lambda。自我执行lambda中的i值将隐藏原始i的值,所有内容都将按预期工作:

1
2
3
4
5
6
7
function create() {
    for (var i = 0; i < 5; i++) (function(i) {
        closures[i] = function() {
            alert("i =" + i);
        };
    })(i);
}

另一种解决方案是创建另一个闭包,它捕获i的正确值,并将其分配给另一个变量,该变量将在最终lambda中"捕获":

1
2
3
4
5
6
7
8
9
function create() {
    for (var i = 0; i < 5; i++) (function() {
        var x = i;

        closures.push(function() {
            alert("i =" + x);
        });
    })();
}


我想这可能是你想要的:

1
2
3
4
5
6
7
8
9
10
11
12
13
var closures = [];

function createClosure(i) {
    closures[i] = function() {
        alert("i =" + i);
    };
}

function create() {
    for (var i = 0; i < 5; i++) {
        createClosure(i);
    }
}

是的,这里正在关闭。每次循环正在创建的函数时,都会抓取i。您创建的每个函数共享相同的i。您看到的问题是,由于它们都共享相同的i,它们也共享i的最终值,因为它是相同的捕获变量。

编辑:这篇由Skeet先生撰写的文章在一定程度上解释了闭包,并以一种比我在这里更具信息性的方式解决了这个问题。但是要小心,因为JavaScript和C句柄闭包有一些细微的区别。跳到"比较捕获策略:复杂性与力量"一节,了解他对这个问题的解释。


JohnResig的学习高级JavaScript解释了这一点。这是一个交互式的演示,解释了很多关于javascript的内容,并且这些示例的阅读和执行都很有趣。

它有一个关于闭包的章节,这个例子看起来很像您的例子。

下面是一个坏例子:

1
2
3
4
5
6
var count = 0;
for ( var i = 0; i < 4; i++ ) {
  setTimeout(function(){
    assert( i == count++,"Check the value of i." );
  }, i * 200);
}

修复:

1
2
3
4
5
6
var count = 0;
for ( var i = 0; i < 4; i++ ) (function(i){
  setTimeout(function(){
    assert( i == count++,"Check the value of i." );
  }, i * 200);
})(i);

只需定义一个内部函数,或将其分配给某个变量:

1
closures[i] = function() {...

不创建整个执行上下文的私有副本。在最近的外部函数退出之前,不会复制上下文(此时,这些外部变量可能被垃圾收集,因此我们最好获取一个副本)。

这就是为什么将另一个函数包装在内部函数周围是可行的——中间人实际上执行并退出,对最内部的函数进行预处理以保存自己的堆栈副本。


下面是您应该做什么来实现您的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var closures = [];
function create() {  
    for (var i = 0; i < 5; i++) {  
        closures[i] = function(number) {      
        alert("i =" + number);  
        };  
    }
}
function run() {  
    for (var i = 0; i < 5; i++) {  
        closures[i](i);
    }
}
create();
run();