2

假设我们有一个函数:

function foo() {
    var x = 10;         
    function bar() {
        var y = 20; 
        return x + y;   
    }
    return bar();
}

console.log(foo());

这在内存模型中会是什么样子。到目前为止,这就是我想象的堆栈上的样子?

TOP OF THE STACK 
-------------- 
bar()
y = 20
return x + 20
-------------- 
foo()
x= 10
bar()
--------------
BOTTOM OF THE STACK 

词法作用域是什么样的 bar 如何知道 x 是什么?foo()在堆上吗?还是bar()有指向的指针foo()

4

1 回答 1

2

好吧,调用foo完成后,在调用期间创建的所有内容都符合垃圾回收 (GC) 条件,因为该代码中的任何内容都没有保留在调用期间创建的任何内容。更有趣的问题是如果foo 返回 bar(函数,而不是bar()调用产生的数字bar)会发生什么。

但是使用您拥有的代码,这是您调用时发生的情况的理论foo(在规范的§10.4.3中定义):

  1. 引擎创建了一个新的声明性环境,它最初是用于特定调用的词法环境变量环境foo(通常它们不会分开;with关键字可以将它们分开,但大多数人不使用它)。该声明性环境具有与之关联的绑定对象。

  2. 任何声明的参数foo,名称,声明中的foo任何变量,通过函数声明声明的任何函数的名称,以及其他一些东西(以定义的顺序)被创建为该绑定对象的属性(详细信息见第 10.5 节) .foovar

  3. 创建bar函数的过程(在§13.2中描述)将调用的词法环境附加foobar函数作为其[[Scope]]属性(不是您可以在代码中使用的文字名称,而是在规范中使用的名称)。

  4. x绑定对象(例如,x变量)的属性获取值10

  5. 调用bar创建一个全新的声明性环境等,使用y变量。新环境的绑定对象具有指向创建它的环境的绑定对象的链接。该环境将bar'[[Scope]]属性作为其外部词法环境引用。

  6. y最里面的绑定对象的属性获取值20

  7. 表达式x + y被评估:

    1. 引擎尝试解析x以获得它的值。首先,它查看最里面的绑定对象,看看它是否有一个名为 的属性x,但它没有。

    2. 引擎转到当前的外部词法环境,看看是否在其绑定对象上有一个x属性。既然这样做了,引擎就会读取属性的值并在表达式中使用它。

    3. 引擎尝试解析y以获得它的值。首先,它查看最里面的绑定对象,看看它是否有一个名为y;的属性。确实如此,因此引擎将该值用于表达式。

  8. 引擎通过添加20to 来完成表达式10,将结果压入堆栈,然后返回 out of bar

  9. 此时,bar可以通过 GC 回收调用的环境和绑定对象。

  10. 引擎从 中获取返回值bar,将其压入堆栈,然后从 中返回foo

  11. 此时,foo可以通过 GC 回收调用的环境和绑定对象。

  12. 代码调用console.log结果。(细节省略。)

所以理论上,没有持久的记忆影响。环境及其绑定对象可以被扔掉。

现在,事实上,现代 JavaScript 引擎非常聪明,并且使用堆栈进行某些对象分配,因此它们不必调用 GC 来回收这些环境和绑定对象。(但请继续阅读。)

现在,假设foo看起来像这样:

function foo() {
    var x = 10;         
    function bar() {
        var y = 20; 
        return x + y;   
    }
    return bar;
}

我们这样做了:

var b = foo();

现在,foo返回一个引用bar(不调用它)。

上面的步骤 1-4 没有改变,但不是调用 bar,而是foo返回对它的引用。这意味着通过调用创建的环境和绑定对象foo 符合 GC 条件,因为bar在该调用期间创建的函数具有对它们的引用,并且我们具有对该函数的引用(通过b变量)。所以理论上在那个时候,堆上存在这样的东西:

+-----+ +-------------+
| b |---->| 功能 |
+-----+ +-------------+
            | 名称:“酒吧” | +----------------+
            | [[范围]] |---->| 环境 |
            +-------------+ +----------------+ +---------+
                                | 绑定对象 |---->| x: 10 |
                                +----------------+ +--------+

因此,如果现代引擎能够巧妙地将这些对象分配到堆栈上(有时),那么它们在foo返回后如何仍然存在?您必须深入研究各个引擎的内部结构才能确定。有些人可能会执行静态分析以查看这种情况是否可能,如果绑定对象可以存活,则从一开始就使用堆分配。有些人可能只是确定何时foo返回应该存活的内容并将这些内容从堆栈复制到堆中。或者 [在此处插入非常聪明的编译器编写者的东西]。一些引擎可能足够聪明,只保留可能被引用的东西(因此,如果您的变量foo从未以任何方式被 引用bar,它们可能会从绑定对象中删除)。高级别的,规范要求它看起来就像上面的结构保留在内存中一样,我们无法在代码中做任何事情来证明这不是发生的事情。

如果我们然后调用b,我们将继续执行上面的步骤,执行步骤 5 到 10,但是当b返回时,上面的结构继续存在。

这就是 JavaScript闭包的工作方式。

于 2014-10-01T21:21:52.500 回答