理解 Javascript/Node 中闭包的变量捕获

Posted

技术标签:

【中文标题】理解 Javascript/Node 中闭包的变量捕获【英文标题】:Understanding variable capture by closures in 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 次。为什么?我对参考/一般文章更感兴趣,而不是解释这个具体的捕获案例。

【问题讨论】:

【参考方案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

【讨论】:

以上是关于理解 Javascript/Node 中闭包的变量捕获的主要内容,如果未能解决你的问题,请参考以下文章

闭包的理解

闭包的理解

闭包理解

闭包理解

关于闭包的理解

闭包的简单理解