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

时间:2012-04-10 19:47:57

标签: javascript node.js closures

除了标准之外,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次。为什么?我对参考/一般文章比对解释捕获的具体情况更感兴趣。

5 个答案:

答案 0 :(得分:11)

我没有方便的参考。但最重要的是:在第一个中,您明确地将i传递给匿名函数,该函数创建了一个新范围。您没有在第二个ij中创建新范围。此外,JavaScript始终捕获变量,而不是值。所以你也可以修改我。

JavaScript var关键字具有功能范围,而不是块范围。因此for循环不会创建范围。

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

答案 1 :(得分:5)

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

我不知道有关此主题的任何明确的文章。

javascript中的变量的范围是功能级别。 javascript中没有块作用域。因此,如果您想为for循环的每次转换使用新版本的变量,则必须使用新函数(创建函数闭包)来每次通过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这个术语。但是,重点是只有函数作用域,因此函数中任何地方声明的变量实际上在函数顶部声明一次,然后分配给函数中的任何位置。

答案 2 :(得分:4)

Mozilla开发者网络有一个非常好的写作:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures

答案 3 :(得分:3)

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

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

在你的第一个例子中,情况有点不同。通过调用包装函数(以及顺便阴影)将i的调用包装在它自己的匿名函数中并将i的值绑定到函数本地作用域中函数参数i中的变量i,您可以在时刻捕获变量i的值,而不是保留随附的引用< / em>到x,其值在内部匿名函数的外壳中发生变化。

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

答案 4 :(得分:0)

对于第一个示例,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