JavaScript 闭包在哪里?

IT技术 javascript closures
2021-03-03 21:46:18

我写了这段代码来自学 JavaScript 闭包:

function1 = function(){
  var variable = "foo"
  var function2 = function(argument){
    console.log(variable + argument);
  }
  return function2
}

function3 = function1();
function3("bar");

这会按预期打印“foobar”。但是变量在哪里呢?

它会成为 function3 的一个属性,还是存储在 function3 的其他地方?JavaScript 是否遍历某种闭包链,类似于它遍历原型链的方式?它是否存储在其他地方的内存中?

我试图更深入地理解这一点。

5个回答

tl;博士:

变量在哪里?

在它被定义的环境中。

它会成为 function3 的一个属性,还是存储在 function3 的其他地方?

不。

JavaScript 是否遍历某种闭包链,类似于它遍历原型链的方式?

是的。

它是否存储在其他地方的内存中?

是的。


tl;博士2:

函数保持对创建它们的环境的引用。当一个函数被调用时,它会创建一个新环境,其父环境是函数保持引用的环境。


更长的解释:

每当执行一个函数时,就会创建一个新的词法环境环境有两个“字段”:一个是跟踪所有变量环境记录和一个外部词法环境,顾名思义,它指的是“父词法环境”。

因此,当我们评估您的代码示例时,内存的初始状态(在执行任何操作之前)可能如下所示(简化):

+-(Global) lexical environment-+     +-Environment Record-+
+-------------+----------------+     +---------+----------+
| Environment |       *--------+---> |function1|undefined |
|   Record    |                |     +---------+----------+
+-------------+----------------+     |function3|undefined |
|    Outer    |                |     +---------+----------+
|   lexical   |    (empty)     |
| environment |                |
+-------------+----------------+

全局环境没有任何外部环境,因为它位于顶部。function1function3是两个尚未初始化的绑定(尚未评估分配)。

创建函数(求值function1 = function() { ... })后,内存如下所示:

            +------------------------------------------------------------------------+
            |                                                                        |
            v                                                                        |
+-(Global) lexical environment-+    +-Environment Record-+     +-----Function Object-+---+
+-------------+----------------+    +---------+----------+     +---------------+-----+---+
| Environment |       *--------+--->|function1|    *-----+---->|[[Environment]]|     *   |
|   Record    |                |    +---------+----------+     +---------------+---------+
+-------------+----------------+    |function3|undefined |     |     name      |function1|
|    Outer    |                |    +---------+----------+     +---------------+---------+
|   lexical   |    (empty)     |
| environment |                |
+-------------+----------------+

现在function1有了一个值,一个函数对象。函数对象具有多个内部(例如[[Environment]])和外部(例如name)属性。顾名思义,不能从用户代码访问内部属性。[[Environment]]物业是非常重要的。注意它如何引用创建函数的词法环境!

下一步是执行function3 = function1(),即调用function2正如我一开始所说的,每当一个函数被执行时,就会创建一个新的词法环境。我们来看看刚进入函数的内存:

               +------------------------------------------------------------------------+
               |                                                                        |
               v                                                                        |
   +-(Global) lexical environment-+    +-Environment Record-+     +-----Function Object-+---+
   +-------------+----------------+    +---------+----------+     +---------------+-----+---+
   | Environment |       *--------+--->|function1|          +---->|[[Environment]]|     *   |
   |   Record    |                |    +---------+----------+     +---------------+---------+
+> +-------------+----------------+    |function3|undefined |     |     name      |function1|
|  |    Outer    |                |    +---------+----------+     +---------------+---------+
|  |   lexical   |    (empty)     |
|  | environment |                |
|  +-------------+----------------+
|
|
|
|  +-----lexical environment------+    +-Environment Record-+
|  +-------------+----------------+    +---------+----------+
|  | Environment |       *--------+--->|variable |undefined |
|  |   Record    |                |    +---------+----------+
|  +-------------+----------------+    |function2|undefined |
|  |    Outer    |                |    +---------+----------+
|  |   lexical   |        *       |
|  | environment |        |       |
|  +-------------+--------+-------+
|                         |
+-------------------------+

这看起来与全球环境的结构非常相似!我们有一个词法环境,它有一个带有两个未初始化绑定的环境记录。但现在最大的不同是“外部词法环境”指向全局词法环境。这怎么可能?

在调用function1和创建新的词法环境时,我们将新环境“外部词法环境”字段的值设置为function1's[[Environment]]字段的值是创建作用域链的地方。

现在,在执行之后function1,内存具有以下结构:

               +------------------------------------------------------------------------+
               |                                                                        |
               v                                                                        |
   +-(Global) lexical environment-+    +-Environment Record-+     +-----Function Object-+---+
   +-------------+----------------+    +---------+----------+     +---------------+-----+---+
   | Environment |       *--------+--->|function1|    *-----+---->|[[Environment]]|     *   |
   |   Record    |                |    +---------+----------+     +---------------+---------+
+> +-------------+----------------+    |function3|   |      |     |     name      |function1|
|  |    Outer    |                |    +---------+---+------+     +---------------+---------+
|  |   lexical   |    (empty)     |                  |
|  | environment |                |                  |
|  +-------------+----------------+                  +-------------------------+
|                                                                              |
|             +----------------------------------------------------------------+--------+
|             v                                                                |        |
|  +-----lexical environment------+    +-Environment Record-+                  v        |
|  +-------------+----------------+    +---------+----------+                           |
|  | Environment |       *--------+--->|variable |  'foo'   |     +-----Function Object-+---+
|  |   Record    |                |    +---------+----------+     +---------------+-----+---+
|  +-------------+----------------+    |function2|    *-----+---->|[[Environment]]|     *   |
|  |    Outer    |                |    +---------+----------+     +---------------+---------+
|  |   lexical   |        *       |                               |     name      |function2|
|  | environment |        |       |                               +---------------+---------+
|  +-------------+--------+-------+
|                         |
+-------------------------+

与 like 类似function1function2具有对通过调用创建的环境的引用function2另外,function3指的是我们创建的函数,因为我们从function1.

最后一步:调用function3('bar')

               +------------------------------------------------------------------------+
               |                                                                        |
               v                                                                        |
   +-(Global) lexical environment-+    +-Environment Record-+     +-----Function Object-+---+
   +-------------+----------------+    +---------+----------+     +---------------+-----+---+
   | Environment |       *--------+--->|function1|    *-----+---->|[[Environment]]|     *   |
   |   Record    |                |    +---------+----------+     +---------------+---------+
+> +-------------+----------------+    |function3|   |      |     |     name      |function1|
|  |    Outer    |                |    +---------+---+------+     +---------------+---------+
|  |   lexical   |    (empty)     |                  |
|  | environment |                |                  |
|  +-------------+----------------+                  +-------------------------+
|                                                                              |
|             +----------------------------------------------------------------+--------+
|             v                                                                |        |
|  +-----lexical environment------+    +-Environment Record-+                  v        |
|  +-------------+----------------+    +---------+----------+                           |
|  | Environment |       *--------+--->|variable |  'foo'   |     +-----Function Object-+---+
|  |   Record    |                |    +---------+----------+     +---------------+-----+---+
|+>+-------------+----------------+    |function2|    *-----+---->|[[Environment]]|     *   |
|| |    Outer    |                |    +---------+----------+     +---------------+---------+
|| |   lexical   |        *       |                               |     name      |function2|
|| | environment |        |       |                               +---------------+---------+
|| +-------------+--------+-------+
++------------------------+
 |
 | +-----lexical environment------+    +-Environment Record-+
 | +-------------+----------------+    +---------+----------+
 | | Environment |       *--------+--->|argument |  'bar'   |
 | |   Record    |                |    +---------+----------+
 | +-------------+----------------+
 | |    Outer    |                |
 | |   lexical   |        *       |
 | | environment |        |       |
 | +-------------+--------+-------+
 +------------------------+

与此处类似,创建了一个新环境,其“外部词法环境”字段指向function1调用时创建的环境

现在,查找 的值argument很简单,因为它存在于环境自己的记录中。但是在查找时variable,会发生以下情况:由于它不存在于环境自己的记录中,它会查看其“外部词法环境”的记录。它可以这样做,因为它有一个对它的引用。

很好的答案。我希望更多关于作用域或闭包的答案能够解释词法环境和变量环境之间的相关性,如您在此处所示。
2021-04-23 21:46:18
澄清:在function1定义之后但在执行之前,持有variable的环境记录function2引用 Global 的外部词法环境是否存储在内存中,或者直到function1第 9 行实际调用?如果我正确地关注你,它们在function1被调用之前不会被存储,此时function3现在在全局命名空间中定义。这对我来说很有意义。我有这个权利吗?
2021-05-09 21:46:18
有没有什么特殊的工具可以用来绘制这些 ASCII 图表?
2021-05-12 21:46:18
第二个环境记录仅在function1被调用时创建function3 function1执行定义(并返回值)。
2021-05-15 21:46:18
此外,这如何与with语句创建的动态范围交互它们是单个链的一部分还是有两个平行链?
2021-05-16 21:46:18

每当 JavaScript 执行 function3 函数时,都会创建一个“范围”对象来保存您命名的局部变量作为变量(“foo”)。请注意,您的 JavaScript 代码无法直接访问此范围对象。因此值“foo”可用于内部函数,尽管外部函数已经返回。

JavaScript 是否遍历某种闭包链,类似于它遍历原型链的方式?

是的。“作用域对象形成一个称为作用域链的链,类似于 JavaScript 的对象系统使用的原型链。

闭包是函数和创建它的作用域对象的组合。闭包让你保存状态——因此,它们通常可以用来代替对象”

在此处阅读更多信息:

“每当 JavaScript 执行一个函数时,都会创建一个‘范围’对象来保存在该函数中创建的局部变量。它使用作为函数参数传入的任何变量进行初始化。这类似于所有全局变量和函数所在的全局对象在,但是“...”每次函数开始执行时都会创建一个全新的范围对象”好的,这听起来就像变量确实被垃圾收集并在需要时重新实例化。在这种情况下,存储匿名函数所function3引用的内存块必须包含其作用域。
2021-05-08 21:46:18
@WylliamJudd 坚持的范围对象是函数的范围对象function1在您的示例中),而不是闭包函数本身。每次执行时都会为函数声明的任何局部变量(包括参数)创建一个新变量如果 function1 返回两个或更多函数,它们将引用相同的 function1 范围对象作为闭包。
2021-05-15 21:46:18
我感到困惑的一件事是变量定义是否在退出作用域时被垃圾收集。我首先学习了 Ruby,我不知道变量在退出作用域时是否会被垃圾回收,但它们的行为就像它们一样。关于是否variable收集垃圾然后在需要时重新实例化,或者甚至在全局范围内存储在内存中的任何说明
2021-05-17 21:46:18

变量存在于声明它们的范围内,该范围可以是全局的或函数。

这里的关键字是scope

正如在 MSDN 网站上出色解释的那样

在函数定义中声明的变量是局部变量。每次执行函数时都会创建和销毁它,函数外的任何代码都无法访问它。JavaScript 不支持块作用域(其中一组大括号 {. . .} 定义了一个新的作用域),除非是块作用域变量的特殊情况。

编辑

它实际上比这更复杂一点,请参阅toddmotto关于 JS 范围的帖子。

我将从 toddmotto 链接复制此内容,因为这非常相关: Scope chains establish the scope for a given function. Each function defined has its own nested scope as we know, and any function defined within another function has a local scope which is linked to the outer function - this link is called the chain. It’s always the position in the code that defines the scope. When resolving a variable, JavaScript starts at the innermost scope and searches outwards until it finds the variable/object/function it was looking for.
2021-04-20 21:46:18
@WylliamJudd function3 和 function1 只是对单个匿名函数的引用......如果你能看到它们只是指向匿名函数的指针,那么你就会意识到这里没有可见性中断。
2021-04-27 21:46:18
@WylliamJudd 它指的是诸如ifwhile块之类的东西……它们不像大多数语言那样有自己的范围。
2021-05-03 21:46:18
“在函数定义中声明的变量是局部变量。每次执行函数时都会创建和销毁它,函数外的任何代码都无法访问它。” 但由于关闭,这不是真的。function3完全存在于 之外function1,但它可以访问 中定义的变量function1问题是,它如何访问该变量?变量在此期间存在于何处?它没有在全局范围内定义。如果我尝试在全局范围内调用变量,我会得到ReferenceError: variable is not defined.
2021-05-10 21:46:18
“JavaScript 不支持块作用域(其中一组大括号 {. . .} 定义了一个新的作用域),除非是块作用域变量的特殊情况。” 这意味着什么?{ 内部的块。. .} 定义function1是一个本地范围吗?在该块之外是全局作用域,对吗?我不明白这句话在说什么。
2021-05-14 21:46:18

关闭在MDN网站上有很好的解释但我建议在 Stack Overflow 的答案中阅读这个很棒的解释,这是对您问题的精确答案

这不是财产。只是在记忆中记住了环境。环境意味着函数和作用域和作用域成员。我希望它照亮道路。

有关闭包如何工作的解释,请参阅此答案。

JavaScript 闭包是如何工作的?

在虚拟机级别,每个函数都有自己的词法环境来跟踪这些信息。虚拟机找到哪些变量在闭包中被访问,并将它们存储在堆中,只要任何闭包需要它们,它们就会一直存在。

有关更深入的信息,请参见例如以下两篇精彩文章:

作为记录,我在发布这个问题之前确实阅读了这个 SO 答案。
2021-04-23 21:46:18