有人可以解释js变量提升和堆栈执行的闭包

时间:2014-01-02 12:51:30

标签: javascript closures hoisting

在阅读其他主题时,我遇到了这段代码:

function loadme() {
 var arr = ["a", "b", "c"];
 var fs = [];
 for (var i in arr) {
   var x = arr[i];
   var f = function() { console.log(x) };
   f();
   fs.push(f);
 }
 for (var j in fs) {
   fs[j]();
 }
}

这将输出:a,b,c,c,c,c。作者解释的问题是,x var在函数开始时被提升,因此在循环中使用它时不会保留它的值。我不明白为什么c在第二个console.log上分配给x 3次?有人可以解释一下吗?

2 个答案:

答案 0 :(得分:4)

事实上,这种情况很容易解释,它出现在许多编程语言中,而不仅仅是JavaScript。

让我们首先选择输出序列a,b,c,c,c,c并丢弃前三个元素 - a,b,c,因为这是在第一个f()内执行函数for时打印的内容环。

因此,我们留下了序列c,c,c,当我们遍历fs数组时,它会被打印出来。您没有打印a,b,c的原因是因为您的第一个x循环中的for参数是通过引用捕获的(不确定是否你的f()闭包定义是有效的(请记住,JavaScript中的每个函数都是某种闭包)。

现在,由于在调用console.log(x)时会计算f()表达式,因此捕获的x参数的当前值将作为参数传递。虽然代码可以正常工作,但是当您在第一个f()循环中调用for时,x就是从arr[i]分配的内容,因此您可以获得a,b,c。但是当你退出循环时,x(即使它对外部范围不可用)仍然被f()捕获,但在第一个循环之后它有一个值c(那个它在最后一次迭代中得到了),这是第二次迭代函数时得到的评估结果。

事实上,这可能是许多令人困惑的错误的来源,因此一些语言可以检测到这一点并通知开发人员(例如,C#编译器,当它看到这样的结构时,会产生以下警告:{{1 }})。

要解决此问题,您只需要在第一个Access to modified closure循环中捕获 x的当前值

for

这里我们使用IIFE(立即调用的函数表达式)来捕获for (var i in arr) { var x = arr[i]; var f = (function(x) { return function() { console.log(x) }; })(x); f(); fs.push(f); } 的当前值。

希望这有帮助。

答案 1 :(得分:3)

var x = arr[i];
var f = function() { console.log(x) };

在这两行中,console.log(x)认为只需要打印x。所以,这三个函数都是以这种方式创建的。因此,当您立即执行这些函数时,x的值在每次迭代中都是不同的。但是当循环结束时,变量x会保留它所持有的最后一个值,并且当执行动态创建的函数时,它们只会打印x的值c

因此,为了解决这个问题,您必须为每个动态创建的函数拥有自己的x副本。通常,我们使用函数参数保留变量的当前状态,该变量在循环中发生变化,如此

var f = function(x) { return function() { console.log(x) } }(x);

现在,我们正在创建一个函数并立即执行它,它返回另一个函数,返回的函数实际上打印了x的值。

function(x) {
    return function() {
        console.log(x)
    }
}(x);

我们将x的值作为参数传递给包装函数,现在保留x的当前值。