了解通过 Javascript/Node 中的闭包捕获变量

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10095404/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 08:40:44  来源:igfitidea点击:

Understanding variable capture by closures in Javascript/Node

javascriptnode.jsclosures

提问by nponeccop

Is there a definite source on variable capture in Javascript besides the standard (it's a pain to read the standard)?

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

In the following code iis copied by value:

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

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

So it prints 1..10. process.nextTickis an analog of setTimeout(f,0)in node.

所以它打印 1..10。process.nextTicksetTimeout(f,0)in 节点的模拟。

But in the next code i doesn't seem to be copied:

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

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

It prints 9 10 times. Why? I'm more interested in a reference/general article than in explaining this concrete case of capture.

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

采纳答案by Matthew Flaschen

I don't have a handy reference. But the bottom line is: In the first, you're explicitly passing in ito an anonymous function, which creates a new scope. You are not creating a new scope for either ior jin the second. Also, JavaScript always captures variables, not values. So you would be able to modify i too.

我没有方便的参考。但最重要的是:首先,您显式地传入i一个匿名函数,该函数创建了一个新的作用域。您没有为其中一个ij第二个创建新的范围。此外,JavaScript 总是捕获变量,而不是值。所以你也可以修改 i 。

The JavaScript varkeyword has function scope, not block scope. So a for loop does not create a scope.

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

As a note, the non-standard letkeyword has local scope.

请注意,非标准let关键字具有局部作用域。

回答by jfriend00

It is copied (or assigned) in your second example, it's just that there's only one copy of variable jand it will have the value that it last had in it which will be 9 (the last rev of your for loop). You need a new function closure to create a new copy of a variable for each rev of the forloop. Your second example just has one variable that is common to all revs of your forloop, thus it can only have one value.

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

I don't know of any definitive writeup on this topic.

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

Variables in javascript are scoped to the function level. There is no block scoping in javascript. As such, if you want a new version of a variable for each rev of the for loop, you have to use a new function (creating a function closure) to capture that new value each time through the forloop. Without the function closure, the one variable will just have one value that will be common to all users of that variable.

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

When you declare a variable such as your var j = i;at some location other than the beginning of the function, javascript hoists the definition to the top of the function and your code becomes equivalent to this:

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

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

This is called variable hoistingand is a term you could Google if you want to read more about it. But, the point is that there is only function scope so a variable declared anywhere in a function is actually declared once at the top of the function and then assigned to anywhere in the function.

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

回答by maerics

In JavaScript, functions enclosevariables which were defined in a scope outside of their own in such a way that they have a "living" reference to the variable, not a snapshot of its value at any particular time.

在 JavaScript 中,函数定义在其自身范围之外的变量封装起来,这样它们就拥有对变量的“活”引用,而不是在任何特定时间的值的快照。

So in your second example, you create ten anonymous functions (in process.nextTick(function(){...})) which enclosethe variable j(and i, which always have the same value when the anonymous function is created). Each of these functions use the value of jat a time afterthe outer for-loop has run entirely, so j=i=10at the time that each of the functions is called. That is, first your for-loop runs entirely, then your anonymous functions run and use the value of j, which is already set to 10!

因此,在您的第二个示例中,您创建了 10 个匿名函数 (in process.nextTick(function(){...})),其中包含变量j(并且i,在创建匿名函数时始终具有相同的值)。这些函数中的每j一个都外部 for 循环完全运行使用一次的值,因此j=i=10在调用每个函数时。也就是说,首先您的 for 循环完全运行,然后您的匿名函数运行并使用 的值j,该值已设置为 10!

In your first example, the situation is a little different. By wrapping the call to process.nextTick(...)in it's own anonymous function and by binding the value of iinto a function-local scope by calling the wrapper function (and incidentally shadowingthe old variable iinto the function parameter i), you capture the value of the variable iat that moment, instead of retaining the enclosed referenceto iwhose value changes in the enclosure of the inner anonymous functions.

在您的第一个示例中,情况略有不同。通过将调用包装process.nextTick(...)在它自己的匿名函数中,并通过i调用包装函数将 的值绑定到函数局部作用域中(顺便将旧变量隐藏i到函数参数中i),您可以i在那个时刻捕获变量的值代替保持封闭参考i其值变化的内匿名函数外壳。

To clarify your first example somewhat, try changing the anonymous wrapper function to use an argument named x((function (x) { process.nextTick(...); })(i)). Here we clearly see that xtakes the value in iat the moment the anonymous function is called so it will get each of the values in the for-loop (1..10).

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