词汇范围在内存模型中是什么样的?

时间:2014-10-01 20:53:49

标签: javascript lexical-scope

假设我们有一个功能:

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()

1 个答案:

答案 0 :(得分:1)

嗯,在对foo的调用完成之后,在调用它期间创建的所有内容都符合垃圾收集(GC)的条件,因为该代码中没有任何东西可以保留在电话。更有趣的问题是如果foo 返回 bar会发生什么(函数,而不是bar()调用bar产生的数字。)< / p>

但是根据您拥有的代码,这里是您调用foo(在规范的§10.4.3中定义)时会发生什么的理论:

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

  2. with的任何声明参数,名称foofoo中用foo声明的任何变量,通过函数声明声明的任何函数的名称,以及其他一些东西(按照定义的顺序)在该绑定对象上创建为属性(§10.5中的详细信息)。

  3. 创建var函数的过程(在§13.2中描述)将bar调用的词汇环境附加到foo函数{ {1}}属性(不是您可以在代码中使用的文字名称,而是规范中使用的名称)。

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

  5. x的调用使用10变量创建了一个全新的声明性环境等。新环境的绑定对象具有返回到创建它的环境的绑定对象的链接。该环境将bar y属性作为其外部词汇环境参考。

  6. 最里面的绑定对象上的bar属性获取值[[Scope]]

  7. 评估表达式y

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

    2. 引擎进入当前版本的外部词法环境,以查看 it 在其绑定对象上是否具有x属性。既然如此,引擎会读取属性的值并在表达式中使用它。

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

  8. 引擎通过将y添加到y来完成表达式,将结果推送到堆栈,然后返回20

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

  10. 引擎从bar获取返回值,将其推送到堆栈,然后从bar返回。

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

  12. 代码使用结果调用foo(详情略去。)

  13. 所以从理论上讲,没有持久的记忆影响。可以抛弃环境及其绑定对象。

    现在,在 fact 中,现代JavaScript引擎非常智能,并且使用堆栈进行某些对象分配,这样他们就不必调用GC来回收这些环境和绑定对象。 (但继续阅读。)

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

    console.log

    我们这样做了:

    foo

    现在,function foo() { var x = 10; function bar() { var y = 20; return x + y; } return bar; } 会返回对var b = foo(); 的引用(不会调用它)。

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

    +-----+     +-------------+
    |  b  |---->|   Function  |
    +-----+     +-------------+
                | name: "bar" |     +----------------+
                | [[Scope]]   |---->|   environment  |
                +-------------+     +----------------+     +-------+
                                    | Binding Object |---->| x: 10 |
                                    +----------------+     +-------+
    

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

    如果我们再调用bar,我们会接受上述步骤,执行步骤5到10,但当b返回时,上面的结构仍然存在。

    这是JavaScript 闭包的工作原理。