1

在阅读另一个主题时,我遇到了这段代码:

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 次?有人可以解释吗?

4

2 回答 2

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# 编译器,当它看到这样的结构时,会产生以下警告:)Access to modified closure

要解决此问题,您只需要捕获第一个循环内的当前值:xfor

for (var i in arr) {
    var x = arr[i];

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

    f();
    fs.push(f);
}

这里我们使用 IIFE(立即调用函数表达式)来捕获x.

希望这可以帮助。

于 2014-01-02T13:13:31.390 回答
3
var x = arr[i];
var f = function() { console.log(x) };

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

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

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

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

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

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

于 2014-01-02T13:03:14.980 回答