JavaScript不支持使用局部变量进行闭包吗?

时间:2009-03-13 16:27:25

标签: javascript loops closures

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

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。

我尝试过Rhino和Firefox。

有人可以向我解释这种行为吗? Thx提前。

7 个答案:

答案 0 :(得分:59)

通过添加额外的匿名函数来修复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中引入内存泄漏。

答案 1 :(得分:9)

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

var closures = [];

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

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

答案 2 :(得分: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);
        });
    })();
}

答案 3 :(得分:6)

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

Skeet先生的

编辑 This article在某种程度上解释了封闭问题,并特别以一种比我在这里提供更多信息的方式解决了这个问题。 但请小心,因为Javascript和C#处理闭包的方式有一些细微差别。 跳到“比较捕获策略:复杂性与能力”这一部分,对此有所解释问题。

答案 4 :(得分:3)

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

它有一章关于闭包,而this example看起来很像你的。

这是一个破碎的例子:

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

答案 5 :(得分:1)

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

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

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

这就是为什么在你的内部函数周围包含另一个函数的工作原理 - 中间人实际执行和退出,使用最里面的函数来保存他自己的堆栈副本。

答案 6 :(得分:-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>