JavaScript关闭在哪里?

时间:2016-05-27 20:18:00

标签: javascript closures

我编写了这段代码来自学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 个答案:

答案 0 :(得分:32)

<强> TL; DR:

  

变量在哪里?

在环境中定义了它。

  

它是否成为function3的属性,还是存储在function3中的其他位置?

没有

  

JavaScript是否遍历某种闭包链,类似于它如何遍历原型链?

  

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

tl;博士2:

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

更长的解释:

每当执行一个函数时,都会创建一个新的lexical environment。环境有两个“字段”:一个environment record,其中所有变量都被跟踪,一个外部词汇环境,顾名思义,引用“父词法环境”。

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

+-(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的{​​{1}}字段的值。这是范围链的创建。

现在,在执行[[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 | | | +---------------+---------+ | +-------------+--------+-------+ | | +-------------------------+ 类似,function1引用了通过调用function2创建的环境。此外,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时,会发生以下情况:由于它在环境自己的记录中不存在,因此它会查看其“外部词汇环境”的记录。它可以做到这一点,因为它有一个参考。

答案 1 :(得分:4)

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

  

JavaScript是否遍历某种闭包链,类似于它如何遍历原型链?

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

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

在这里阅读更多内容:

答案 2 :(得分:2)

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

此处的关键字为范围

MSDN网站中的explained brilliantly

  

在函数定义中声明的变量是本地的。它   每次执行函数时都会创建并销毁它   函数外的任何代码都无法访问。 JavaScript确实如此   不支持块范围(其中一组大括号{...}定义了一个   新范围),除了块范围变量的特殊情况。

修改

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

答案 3 :(得分:1)

关闭在MDN网站上得到了很好的解释。但我建议通过this awesome explanation in a Stack Overflow answer, a precise answer to your question

这不是财产。只是在记忆中记住了环境。环境意味着功能以及范围和范围成员。我希望它能开辟道路。

答案 4 :(得分:1)

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

How do JavaScript closures work?

在虚拟机级别,每个函数都有自己的lexical environment,用于跟踪此信息。虚拟机查找在闭包中访问哪些变量并将它们存储在堆上,只要任何闭包可能需要它们,它们就会存在。

有关更深入的信息,请参阅以下两个重要内容: