JavaScript中的对象

16

JavaScript中的原始值存储在堆栈中,而对象存储在堆中。我理解为什么原始值存储在堆栈中,但为什么对象存储在堆中呢?


3
你为什么认为原始值存储在栈中?实际上并不是这样。 - Pointy
3个回答

27

实际上,在JavaScript中,即使原始数据类型也是存储在堆(heap)中,而不是栈(stack)中(见下面分隔线下的注释)。当控制进入一个函数时,将创建一个执行上下文(一个对象)用于该函数的调用,其中有一个变量对象。所有var和函数参数(以及其他一些东西)都是该匿名变量对象的属性,就像命名对象的其他属性一样。使用了调用堆栈(call stack),但规范并不要求堆栈用于“局部”变量存储,并且JavaScript的闭包(closure)使得使用类似C、C++等的栈对其不可行。详细信息请参见规范

相反,使用了一个链式结构(链表)。当您引用一个未限定的符号时,解释器会检查当前执行上下文的变量对象,看看它是否具有该名称的属性。如果是,则使用它;如果没有,则检查作用域链(scope chain)中的下一个变量对象(请注意,这是按词法顺序,而不是调用顺序,例如调用堆栈),依此类推,直到达到全局执行上下文为止(全局执行上下文具有如同其他执行上下文一样的变量对象)。全局EC的变量对象是我们可以直接在代码中访问的唯一一个:在全局作用域代码中,this指向它(以及在任何未显式设置this的函数中调用该函数)。 (在浏览器中,我们有另一种直接访问它的方法:全局变量对象具有一个名为window的属性,它用于指向它自己。)

关于为什么在堆中存储对象的问题,是因为它们可以独立创建和释放。像C、C++等使用堆栈存储局部变量的语言可以这样做,因为变量应该在函数返回时被销毁。堆栈是一种不错的高效方式来实现这一点。但是,对象的创建和销毁方式并不像变量那样直观;同时创建的三个对象可能有截然不同的生命周期,所以对它们来说堆栈并没有意义。由于JavaScript的本地变量存储在对象上,而这些对象的生命周期与函数返回(潜在地)无关......你明白了吧。:-) 在JavaScript中,堆栈基本上只用于返回地址。


然而,值得注意的是,仅仅因为事情如上所述概念上,并不意味着引擎必须在底层这样做。只要它在规范上外部表现得像上面描述的一样,实现(引擎)就可以自由地做他们喜欢的事情。我了解到V8(Google的JavaScript引擎,在Chrome和其他地方使用)做了一些非常聪明的事情,比如在函数内使用堆栈存储局部变量(甚至在函数内部分配本地对象),然后只在必要时将它们复制到堆中(例如,因为执行上下文或其中的单个对象在调用后继续存在)。你可以看到,在大多数情况下,这将最小化堆碎片,并更加积极和高效地回收用于临时变量的内存,因为与大多数函数调用相关联的执行上下文不需要在调用之后继续存在。让我们看一个例子:

function foo() {
    var n;

    n = someFunctionCall();
    return n * 2;
}

function bar() {
    var n;

    n = someFunction();
    setCallback(function() {
        if (n === 2) {
            doThis();
        }
        else {
            doThat();
        }
    });
}
在上面的代码中,像V8这样积极优化的引擎可以检测到对foo进行调用的概念执行上下文在foo返回时不需要保留。因此,V8可以自由地在堆栈上分配该上下文,并使用基于堆栈的清除机制。
相比之下,调用bar所创建的执行上下文必须在bar返回后继续存在,因为有一个闭包(我们传递给setCallback的匿名函数)依赖于它。因此,在编译bar时(因为V8会即时将其编译为机器码),V8可能会使用不同的策略,实际上在堆中分配上下文对象。
(顺便提一下,如果以上任何一种情况以任何方式使用eval,那么很可能V8和其他引擎甚至不会尝试任何形式的优化,因为eval会引入太多的优化失效模式。这也是不使用eval的另一个原因,如果没有必要,几乎永远不需要使用。)
但这些都是实现细节。从概念上讲,事情就像上文所描述的那样。

2
@T.J. Crowder:我知道这已经是四年之后的事情了,但我还是想感谢您提供如此出色的答案。 - Daniel Szabo
在JavaScript中,内存和执行上下文是相同的吗? - Nipuna
@Nips:执行上下文(至少在概念上)是一个对象,因此它占用内存;这并不意味着它“是”内存(许多其他东西也保存在内存中)。 - T.J. Crowder
@T.J.Crowder 例如?:-) - Nipuna
@Nips:包括日常对象、函数、变量绑定对象等任何其他类型的对象({"hey": "I'm an object"});原始值;调用栈... - T.J. Crowder
惊人的回答。与此相关的一些好文章。http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection - KushalSeth

6
对象的大小可以动态增长,因此需要调整它们的内存需求。这就是为什么它们被存储在堆中的原因。

是的,但我无法想象这个场景。你能拿一个对象举例并解释一下吗? - alter
1
简单来说,假设您有一个名为human的对象,其具有年龄和地址属性。在JavaScript中,您可以编程方式添加另一个属性。堆结构易于在任何位置分配或释放内存。由于堆栈顺序增长或减少,因此如果需要向栈中驻留的对象分配额外的内存,则不可能进行分配。希望这让您稍微清楚了一点。 - Shamim Hafiz - MSFT

0

基本值和对象总是存储在其他对象中——它们是某个对象的属性。

没有一个原始值/对象不是另一个对象的属性。(唯一的例外是全局对象)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接