假设我们有一个功能:
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()
?
答案 0 :(得分:1)
嗯,在对 但是根据您拥有的代码,这里是您调用 引擎会创建一个新的声明性环境,它最初是词汇环境和变量环境,用于对{{{ 1}}(通常那些不要分开; 创建 绑定对象的 对 最里面的绑定对象上的 评估表达式 引擎尝试解析 引擎进入当前版本的外部词法环境,以查看 it 在其绑定对象上是否具有 引擎尝试解析 引擎通过将 此时,可以通过GC回收调用 引擎从 此时,可以通过GC回收调用 代码使用结果调用 所以从理论上讲,没有持久的记忆影响。可以抛弃环境及其绑定对象。 现在,在 fact 中,现代JavaScript引擎非常智能,并且使用堆栈进行某些对象分配,这样他们就不必调用GC来回收这些环境和绑定对象。 (但继续阅读。) 现在,假设 我们这样做了: 现在, 上面的步骤1-4没有改变,但是调用 因此,如果现代引擎聪明地将这些对象分配到堆栈(有时),那么在 如果我们再调用 这是JavaScript 闭包的工作原理。foo
的调用完成之后,在调用它期间创建的所有内容都符合垃圾收集(GC)的条件,因为该代码中没有任何东西可以保留在电话。更有趣的问题是如果foo
返回 bar
会发生什么(函数,而不是bar()
调用bar
产生的数字。)< / p>
foo
(在规范的§10.4.3中定义)时会发生什么的理论:
foo
关键字可以将它们分开,但大多数人都不会使用它)。该声明性环境具有与之关联的绑定对象。with
的任何声明参数,名称foo
,foo
中用foo
声明的任何变量,通过函数声明声明的任何函数的名称,以及其他一些东西(按照定义的顺序)在该绑定对象上创建为属性(§10.5中的详细信息)。var
函数的过程(在§13.2中描述)将bar
调用的词汇环境附加到foo
函数{ {1}}属性(不是您可以在代码中使用的文字名称,而是规范中使用的名称)。bar
属性(例如,[[Scope]]
变量)获取值x
。x
的调用使用10
变量创建了一个全新的声明性环境等。新环境的绑定对象具有返回到创建它的环境的绑定对象的链接。该环境将bar
y
属性作为其外部词汇环境参考。bar
属性获取值[[Scope]]
。y
:
20
以获取其值。首先,它查看最里面的绑定对象,看它是否有一个名为x + y
的属性,但它没有。x
属性。既然如此,引擎会读取属性的值并在表达式中使用它。x
以获取其值。首先,它查看最里面的绑定对象,看它是否有一个名为x
的属性;它确实如此,因此引擎将该值用于表达式。y
添加到y
来完成表达式,将结果推送到堆栈,然后返回20
。10
的环境和绑定对象。bar
获取返回值,将其推送到堆栈,然后从bar
返回。bar
的环境和绑定对象。foo
。 (详情略去。) foo
看起来像这样:console.log
foo
function foo() {
var x = 10;
function bar() {
var y = 20;
return x + y;
}
return bar;
}
会返回对var b = foo();
的引用(不会调用它)。foo
代替调用 bar
,bar
返回对它的引用。这意味着通过调用foo
创建的环境和绑定对象不适合GC,因为在该调用期间创建的foo
函数具有对它们的引用,并且我们有对该函数的引用(通过bar
变量)。所以从理论上来说,堆上存在这样的东西:+-----+ +-------------+
| b |---->| Function |
+-----+ +-------------+
| name: "bar" | +----------------+
| [[Scope]] |---->| environment |
+-------------+ +----------------+ +-------+
| Binding Object |---->| x: 10 |
+----------------+ +-------+
b
返回后它们如何仍然存在?您必须深入了解各个引擎的内部结构。有些人可能会执行静态分析以查看情况是否可能,并且如果绑定对象可以存活,则从头开始使用堆分配。有些人可能只是确定foo
何时返回应该存活的内容并将这些内容从堆栈复制到堆中。或者[在这里插入真正聪明的编译器编写器]。有些引擎可能足够聪明,只能保留可能被引用的内容(因此,如果foo
中的变量从未以foo
的方式从未引用过,那么它们可能会从绑定对象中删除)。高级,规范要求似乎就像上面的结构保留在内存中一样,我们在代码中无能为力就能证明这不是发生的事情。bar
,我们会接受上述步骤,执行步骤5到10,但当b
返回时,上面的结构仍然存在。