60

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

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.

我试过犀牛和火狐。有人可以向我解释这种行为吗?

4

7 回答 7

61

通过添加一个额外的匿名函数来修复 Jon 的答案:

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 中引入内存泄漏。

于 2009-03-13T16:57:47.770 回答
9

我认为这可能是你想要的:

var closures = [];

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

function create() {
    for (var i = 0; i < 5; i++) {
        createClosure(i);
    }
}
于 2009-03-13T16:39:43.633 回答
9

解决方案是让一个自动执行的 lambda 包装您的数组推送。您还将 i 作为参数传递给该 lambda。自动执行的 lambda 中的 i 值将影响原始 i 的值,并且一切都将按预期工作:

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

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

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

        closures.push(function() {
            alert("i = " + x);
        });
    })();
}
于 2009-03-13T16:42:50.223 回答
6

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

编辑: Skeet 先生的 这篇文章更深入地解释了闭包,并特别以一种比我在这里提供的信息更丰富的方式解决了这个问题。 但是要小心,因为 Javascript 和 C# 处理闭包的方式有一些细微的差别。 跳到“比较捕获策略:复杂性与功率”一节,了解他对这个问题的解释。

于 2009-03-13T16:30:09.507 回答
4

John Resig 的Learning Advanced JavaScript解释了这一点以及更多内容。这是一个交互式演示文稿,解释了很多关于 JavaScript 的内容,示例阅读和执行都很有趣。

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

这是损坏的示例:

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

和修复:

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);
于 2009-03-13T17:24:33.243 回答
1

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

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

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

这就是为什么在你的内部函数周围包装另一个函数有效的原因——中间人实际上执行并退出,提示最里面的函数保存他自己的堆栈副本。

于 2010-10-27T23:18:39.233 回答
-1

以下是你应该做的来达到你的结果:

<script>
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();
</script>
于 2009-03-13T16:40:56.720 回答