如何在Javascript中为变量分配内存?

时间:2010-05-10 05:16:51

标签: javascript memory-management

我想知道局部变量是如何在javascript中分配内存的。 在C和C ++中,局部变量存储在堆栈中。它在javascript中是一样的吗?或者一切都存储在堆中?

2 个答案:

答案 0 :(得分:52)

这实际上是一个非常有趣的Javascript领域。 the spec中的详细信息,但是:Javascript处理局部变量的方式与C的方式完全不同。当你调用一个函数时,除此之外还会创建一个用于该调用的“变量环境”,它具有一个称为“绑定对象”的东西。 (简称为“变量对象”;说“变量环境的绑定对象”只是一个啰嗦的 tad 位!)变量对象具有属性对于函数的参数,函数中声明的所有局部变量,以及函数内声明的所有函数(以及其他一些东西)。首先根据变量对象检查函数中的非限定引用(例如,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时,会使用以下属性创建变量对象:

  • ab - 函数的参数
  • c - 函数
  • 中声明的局部变量
  • bar - 在函数
  • 中声明的函数
  • (......以及其他一些事情)

foo执行语句c = a + b;时,它会引用变量对象上的cab属性,以便调用{{1} 1}}。当foo返回对其中声明的foo函数的引用时,bar将继续调用bar返回。由于foobar的特定调用的变量对象具有(隐藏)引用,因此变量对象存活(而在正常情况下,它将没有未完成的引用,因此可用于垃圾收集)。

稍后,当我们调用foo时,会创建一个用于该调用的 new 变量对象(其中包括)一个名为bar的属性 - d的参数1}}。 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                      |
+----------------------------+

实现可以自由使用他们想要的任何机制来使上面的看起来发生。对于函数调用,不可能直接访问变量对象,并且规范明确指出,如果变量对象只是一个概念,而不是实现的文字部分,那就完全没问题了。一个简单的实现可能只是字面上做的规范说;当没有涉及闭包时(为了速度利益),更复杂的一个可以使用堆栈,或者可以总是使用堆栈但是在弹出堆栈时“撕掉”闭包所需的变量对象。在任何特定情况下,唯一知道的方法是查看他们的代码。 : - )

有关闭包,范围链等的更多信息,请点击此处:

答案 1 :(得分:20)

不幸的是,答案是:这取决于。

最近的javascript引擎发生了重大转变,开始优化比以前更好。答案过去是:"局部变量存储在堆分配的堆栈帧中,以便闭包工作"。它不再那么简单了。

对于Scheme实现和闭包优化已经(或曾经是20 - 30年前)的研究(JavaScript继承了很多Scheme闭包,除了让它更加棘手的延续)。

我没有准备好纸质链接,但如果你没有非常高效的垃圾收集器,你也需要使用堆栈。棘手的部分是处理闭包,闭包需要堆分配的变量。因为使用了不同的策略。结果是混合:

    通过内联函数
  • ,可以显着减少分配/释放的堆分配帧数
  • 一些变量可以安全地放在堆栈上,因为它的时间跨度是有限的(它通常也连接到内联函数调用)
  • 在某些情况下,您知道您可能正在创建闭包,但您可以等到发生这种情况,然后为其分配堆栈帧并从堆栈复制当前值
  • 有一些优化连接到尾部调用,你可以在那里先进行堆分配,然后重复使用堆栈帧进行下一个函数调用,但据我所知,这在javascript引擎中没有使用

这个领域在几个竞争引擎中变化非常快,所以答案可能仍然是"它取决于"

此外,在该语言的新版本中,我们将看到letconst等功能,这些功能实际上使引擎更容易优化分配决策。特别是不变性非常有用,因为你可以在堆栈中自由地复制值(例如,然后制作闭包对象的一部分),而不解决来自不同闭包的变化变量的冲突。