在Javascript中如何分配变量内存?
我想知道本地变量如何在javascript中分配内存。 在C和C ++中,局部变量存储在堆栈中。 在JavaScript中它是一样的吗? 或者一切都存储在堆?
这实际上是一个非常有趣的Javascript领域。 规范中的细节,但是:Javascript处理局部变量的方式与C的方式完全不同。 当你调用函数时,除了别的以外,还会创建一个用于该调用的“变量环境”,其中有一些称为“绑定对象”的东西。 (简称为“变量对象”;称为“变量环境的绑定对象”只是有点冗长!)变量对象具有函数参数的属性,函数中声明的所有局部变量,以及在函数中声明的所有函数(以及其他一些事情)。 首先检查函数中的非限定引用(例如, foo
中的foo
,而不是obj.foo
),以检查它们是否与它的属性相匹配; 如果他们这样做,则使用这些属性。
当一个闭包在返回的函数中存在(这可能由于多种原因而发生)时,该函数调用的变量对象通过闭包的引用保留在内存中。 乍一看,这表明栈不用于本地变量; 实际上,现代JavaScript引擎非常聪明,并且可能(如果值得的话)使用堆栈实际上并未被闭包使用。 (当然,栈仍然用于跟踪返回地址等)。
这是一个例子:
function foo(a, b) {
var c;
c = a + b;
function bar(d) {
alert("d * c = " + (d * c));
}
return bar;
}
var b = foo(1, 2);
b(3); // alerts "d * c = 9"
当我们调用foo
,会用这些属性创建一个变量对象:
a
和b
- 函数的参数 c
- 函数中声明的局部变量 bar
- 在函数中声明的函数 当foo
执行语句c = a + b;
,它引用foo
调用的变量对象上的c
, a
和b
属性。 当foo
返回对其内部声明的bar
函数的引用时, bar
保持foo
返回的调用。 由于bar
对该特定的foo
调用的变量对象具有(隐藏的)引用,所以变量对象仍然存在(而在正常情况下,它没有未完成的引用,因此可用于垃圾收集)。
之后,当我们调用bar
,为该调用创建一个新的变量对象,其中包含一个名为d
的属性 - bar
的参数。 首先检查bar
非限定引用,并针对该调用的变量对象进行检查; 因此,例如, d
解析为变量对象的d
属性以调用bar
。 但是,与其变量对象上的属性不匹配的非限定引用将针对bar
的“范围链”中的下一个变量对象进行检查,该对象是调用foo
的变量对象。 而且因为它有一个属性c
,这是bar
内使用的属性。 例如,粗略地说:
+----------------------------+ | `foo` call variable object | | -------------------------- | | a = 1 | | b = 2 | | c = 3 | | bar = (function) | +----------------------------+ ^ | chain | +----------------------------+ | `bar` call variable object | | -------------------------- | | d = 3 | +----------------------------+
实现可以自由地使用他们想要的任何机制来实现上述目标。 对于函数调用来说,直接访问变量对象是不可能的,而且规范清楚地表明,如果变量对象只是一个概念,而不是实现的文字部分,那就完全没问题。 一个简单的实现可能只是字面上做规范说什么; 一个更复杂的可以在没有包含闭包(为了速度利益)时使用堆栈,或者可以总是使用堆栈,但是当弹出堆栈时“关闭”关闭所需的变量对象。 在任何特定情况下知道的唯一方法是查看他们的代码。 :-)
关于闭包,范围链等的更多信息请见:
不幸的是答案是:这取决于。
最近的javascript引擎发生了很大的转变,开始比以前更好的优化。 过去的答案是:“局部变量存储在堆分配堆栈帧中,以便闭包工作”。 它不再那么简单了。
曾经(或曾经是20-30年前)研究Scheme实现和闭包优化(JavaScript继承了相当多的Scheme闭包,除了使其更加棘手的连续性之外)。
我没有准备好纸张链接,但是如果你没有非常高效的垃圾收集器,你也需要使用堆栈。 然后棘手的部分是处理闭包,这需要变量堆分配。 为此使用不同的策略。 其结果是一个混合体,其中:
这个领域在几个竞争引擎中变化非常快,所以答案可能仍然是“取决于”
另外,在新版本的语言中,我们将看到像let
和const
这样的功能,这些功能实际上使引擎更容易优化分配决策。 特别是不变性非常有用,因为您可以自由地将值从堆栈中复制出来(并且例如使其成为闭包对象的一部分),而不必解决来自不同闭包的变化变量的冲突。