【问题标题】:Understanding variable capture by closures in Javascript/Node理解 Javascript/Node 中闭包的变量捕获
【发布时间】:2012-04-23 03:16:08
【问题描述】:

除了标准之外,Javascript中的变量捕获是否有明确的来源(阅读标准很痛苦)?

在下面的代码中i是按值复制的:

for (var i = 0; i < 10; i++)
{
    (function (i)
    {
        process.nextTick(function ()
        {
            console.log(i)
        })
    }) (i)
}

所以它打印 1..10。 process.nextTick 是节点中 setTimeout(f,0) 的模拟。

但在接下来的代码中,我似乎没有被复制:

for (var i = 0; i < 10; i++)
{
        var j = i
        process.nextTick(function ()
        {
            console.log(j)
        })
}

它打印 9 10 次。为什么?我对参考/一般文章更感兴趣,而不是解释这个具体的捕获案例。

【问题讨论】:

    标签: javascript node.js closures


    【解决方案1】:

    我没有方便的参考资料。但底线是:首先,您将i 显式传递给匿名函数,该函数创建了一个新范围。您不会在第二个中为 ij 创建新范围。此外,JavaScript 总是捕获变量,而不是值。所以你也可以修改 i。

    JavaScript var 关键字具有函数作用域,而不是块作用域。所以 for 循环不会创建作用域。

    请注意,非标准 let 关键字具有本地范围。

    【讨论】:

    • @nponeccop,JavaScript 有函数作用域。
    • 我的头正撞在桌子上。不知道,假设它是 C++ 或 Perl 或 Haskell :) 令人着迷
    • 如果有人需要,节点支持let--harmony_block_scoping 命令行参数
    • ` JavaScript 总是捕获变量,而不是值。 `这是从哪里来的?
    • 附带说明,let 关键字从那时起已经标准化。至于“变量,而不是值”,大多数语言都捕获变量。但是例如现代 C++ 可以按值捕获。
    【解决方案2】:

    它在您的第二个示例中被复制(或分配),只是变量j 只有一个副本,并且它将具有它最后具有的值,即 9(您的 for环形)。您需要一个新的函数闭包来为 for 循环的每个 rev 创建一个变量的新副本。您的第二个示例只有一个变量,该变量对您的 for 循环的所有转速都是通用的,因此它只能有一个值。

    我不知道关于这个主题的任何权威文章。

    javascript 中的变量的范围仅限于函数级别。 javascript 中没有块作用域。因此,如果您想为 for 循环的每个 rev 提供一个新版本的变量,您必须使用一个新函数(创建一个函数闭包)每次通过 for 循环来捕获该新值。如果没有函数闭包,一个变量将只有一个值,该值对该变量的所有用户都是通用的。

    当您在函数开头以外的某个位置声明诸如 var j = i; 之类的变量时,javascript 会将定义提升到函数顶部,您的代码将与此等效:

    var j;
    for (var i = 0; i < 10; i++)
    {
            j = i;
            process.nextTick(function ()
            {
                console.log(j)
            })
    }
    

    这称为variable hoisting,如果您想了解更多相关信息,可以通过 Google 搜索。但是,关键是只有函数作用域,所以在函数中任何地方声明的变量实际上是在函数顶部声明一次,然后分配给函数中的任何地方。

    【讨论】:

    • 更新了我的回答,详细说明了您的情况。
    【解决方案3】:

    在 JavaScript 中,函数包含在其自身范围之外定义的变量,这样它们对变量具有“实时”引用,而不是其值的快照特定时间。

    所以在你的第二个例子中,你创建了十个匿名函数(在process.nextTick(function(){...}) 中),其中包含变量j(和i,当匿名函数时它们总是具有相同的值被建造)。这些函数中的每一个都在外部for循环完全运行之后使用j的值,因此在调用每个函数时j=i=10。也就是说,首先你的 for 循环完全运行,然后你的匿名函数运行并使用 j 的值,它已经设置为 10!

    在您的第一个示例中,情况略有不同。通过在它自己的匿名函数中包装对process.nextTick(...) 的调用,并通过调用包装函数将i 的值绑定到函数局部范围内(顺便遮蔽旧变量i进入函数参数i),您在那个时刻捕获变量i的值,而不是保留值发生变化的i封闭引用在内部匿名函数的外壳中。

    为了稍微澄清您的第一个示例,请尝试将匿名包装函数更改为使用名为 x ((function (x) { process.nextTick(...); })(i)) 的参数。在这里我们清楚地看到x 在调用匿名函数的那一刻获取i 中的值,因此它将获取for循环中的每个值(1..10)。

    【讨论】:

    • 捕获价值而不是保留封闭参考的最佳实践解决方案是什么?
    • @JarrodSmith 在您关闭之前声明一个 const value = ...,您只会捕获原来的值。
    【解决方案4】:

    对于第一个示例,i 不一样。

    for (var i = 0; i < 10; i++) {
        (function (i) { // ? capture i as the new i in this closure scope.
            process.nextTick(function () {
                console.log(i) // ? reference new i (captured).
            })
        })(i)
    }
    

    ?Closures/Value capture - Rosetta Code

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-28
      • 1970-01-01
      • 2014-03-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-14
      相关资源
      最近更新 更多