内存模型中的词汇范围是什么样的? [英] What does lexical scope look like in memory model?
问题描述
说我们有一个功能:
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
).
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):
-
引擎创建一个新的声明性环境,该环境最初是词法环境和可变环境,用于对
(并且通常不会分开; with
关键字可以将它们分开,但是大多数人不使用它).该声明性环境具有与之关联的绑定对象.
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; thewith
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).
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
的计算结果:
-
引擎尝试解析
x
以获取其值.首先,它查看最里面的绑定对象,以查看它是否具有名为x
的属性,但没有.
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 calledx
, 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屋!