理解 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
显式传递给匿名函数,该函数创建了一个新范围。您不会在第二个中为 i
或 j
创建新范围。此外,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 中闭包的变量捕获的主要内容,如果未能解决你的问题,请参考以下文章