内存模型中的词汇范围是什么样的? [英] What does lexical scope look like in memory model?

查看:95
本文介绍了内存模型中的词汇范围是什么样的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

说我们有一个功能:

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

console.log(foo());

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

What would this look like in a memory model. So far this is what I imagine it looks like on the stack?

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

词法范围看起来是什么样的,bar如何知道x是什么? foo()是否在堆上?还是bar()有指向foo()的指针?

What does lexical scope look like how does bar know what x is? Is foo() on the heap? or does bar() have a pointer to foo()?

推荐答案

之后,对foo的调用完成,在调用过程中创建的所有内容都可以进行垃圾回收(GC) ,因为该代码中没有任何内容可以保留调用期间创建的任何内容.更有趣的问题是,如果foo 返回 bar(该函数,而不是bar()调用bar的结果),会发生什么情况.

Well, after the call to foo completes, everything created during the call to it is eligible for garbage collection (GC), because nothing in that code is holding onto anything created during the call. The more interesting question would be what happens if foo returns bar (the function, not bar() the number resulting from calling bar).

但是有了代码,这就是调用foo时发生的理论(在

But with the code you have, here's the theory of what happens when you call foo (defined in §10.4.3 of the spec):

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

  1. The engine creates a new declarative environment which is initially the lexical environment and variable environment for that specific call to foo (and normally those don't separate; the with keyword can separate them, but most people don't use it). That declarative environment has a binding object associated with it.

foo的任何已声明参数,名称foo,用var声明的foo中的任何变量,通过函数声明声明的任何函数的名称以及其他一些其他事项是( (按定义的顺序)创建为该绑定对象上的属性(详细信息请参见§ 10.5 ).

Any declared arguments to foo, the name foo, any variables within foo declared with var, the names of any functions declared via function declarations, and a couple of other things are (in a defined order) created as properties on that binding object (details in §10.5).

创建bar函数的过程(在

The process of creating the bar function (described in §13.2) attaches the lexical environment of the call to foo to the bar function as its [[Scope]] property (not a literal name you can use in code, but the name used in the spec).

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

The x property of the binding object (e.g., the x variable) gets the value 10.

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

The call to bar creates a whole new declarative environment, etc., with the y variable. The new environment's binding object has a link back to the binding object for the environment in which it was created. That environment gets bar's [[Scope]] property as its outer lexical environment reference.

最里面的绑定对象上的y属性获得值20.

The y property on the innermost binding object gets the value 20.

表达式x + y的计算结果:

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

  1. The engine tries to resolve x to get its value. First it looks at the innermost binding object to see if it has a property called x, but it doesn't.

引擎转到当前引擎的外部词汇环境,以查看 it 的绑定对象是否具有x属性.既然如此,引擎将读取该属性的值并在表达式中使用它.

The engine goes to the outer lexical environment of the current one to see if it has an x property on its binding object. Since it does, the engine reads the value of the property and uses that in the expression.

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

The engine tries to resolve y to get its value. First it looks at the innermost binding object to see if it has a property called y; it does, and so the engine uses that value for the expression.

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

  • The engine completes the expression by adding 20 to 10, pushes the result on the stack, and returns out of bar.

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

    At this point, the environment and binding object for the call to bar can be reclaimed via GC.

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

    The engine takes the return value from bar, pushes it on the stack, and returns from foo.

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

    At this point, the environment and binding object for the call to foo can be reclaimed via GC.

    代码将使用结果调用console.log. (省略详细信息.)

    The code calls console.log with the result. (Details omitted.)

    因此,从理论上讲,没有持久的内存影响.可以扔掉环境及其绑定对象.

    So in theory, no enduring memory impact. The environments and their binding objects can be tossed.

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

    Now, in fact, modern JavaScript engines are really smart, and use the stack for certain object allocations so that they don't have to invoke GC to reclaim these environments and binding objects. (But keep reading.)

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

    Now, suppose foo looked like this:

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

    我们做到了:

    var b = foo();
    

    现在,foo返回对bar的引用(不调用它).

    Now, foo returns a reference to bar (without calling it).

    上面的步骤1-4不变,但是foo代替了对它们的引用,而不是调用 bar.这意味着通过调用foo 创建的环境和绑定对象不符合的条件,因为该调用期间创建的bar函数具有对它们的引用,而我们对此具有引用函数(通过b变量).因此从理论上讲,堆上存在这样的内容:

    Steps 1-4 above are unchanged, but then instead of calling bar, foo returns a reference to it. That means that the environment and binding object created by calling foo are not eligible for GC, because the bar function created during that call has a reference to them, and we have a reference to that function (via the b variable). So in theory at that point, something like this exists on the heap:

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

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

    So if modern engines are smart about allocating these objects on stack (sometimes), how can they still exist after foo returns? You'd have to dig into the internals of individual engines to be sure. Some probably perform static analysis to see whether the situation is possible and use heap allocation from the start if the binding object can survive. Some may just determine when foo is returning what should survive and copy those things from the stack to the heap. Or [insert really smart compiler writer stuff here]. Some engines may be smart enough to only retain the things that can possibly be referenced (so if you had variables in foo that were never referenced in any way by bar, they might be pruned from the binding object). High-level, the spec requires that it seem like the structure above is retained in memory, that nothing we can do in our code can prove that that isn't what happened.

    如果我们随后调用b,则按照上面的步骤进行操作,执行步骤5到10,但是当b返回时,上面的结构仍然存在.

    If we then call b, we pick up with the steps above, executing Steps 5 through 10, but when b returns, the structure above continues to exist.

    这是JavaScript 闭包的工作方式.

    This is how JavaScript closures work.

    这篇关于内存模型中的词汇范围是什么样的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆