Javascript Javascript中变量是如何分配内存的?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2800463/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How variables are allocated memory in Javascript?
提问by subbu
I would like to know how local variables are allocated memory in javascript. In C and C++ local variables are stored on stack. Is it the same in javascript? or everything is stored in heap?
我想知道如何在 javascript 中为局部变量分配内存。在 C 和 C++ 中,局部变量存储在堆栈中。在javascript中是一样的吗?或者一切都存储在堆中?
回答by T.J. Crowder
It's actually a very interesting area of JavaScript, and there are at least two answers:
它实际上是 JavaScript 一个非常有趣的领域,至少有两个答案:
- An answer in terms of what the specificationdefines, and
- An answer in terms of what JavaScript engines actually do, which may be optimized (and often is)
- 关于规范定义的答案,以及
- 关于 JavaScript 引擎实际做什么的答案,可以优化(通常是)
In terms of the specification: JavaScript's way of handling local variables is quite different from the way C does it. When you call a function, amongst other things a lexical environmentfor that call is created, which has something called an environment record. To keep things simple, I'm going to refer to them both together as the "binding object" (there's a good reason they're separate in the specification, though; if you want to get deeper into it, set aside a few hours and read through the spec). The binding object contains bindingsfor the arguments to the function, all local variables declared in the function, and all functions declared within the function (along with a couple of other things). A bindingis a combination of a name (like a) and the current value for the binding (along with a couple of flags we don't need to worry about here). An unqualified reference within the function (e.g., the fooin foo, but not the fooin obj.foo, which is qualified) is first checked against the binding object to see if it matches a binding on it; if it does, that binding is used. When a closure survives the function returning (which can happen for several reasons), the binding object for that function call is retainedin memory because the closure has a reference to the binding object in place where it was created. So in specification terms, it's all about objects.
在规范方面:JavaScript 处理局部变量的方式与 C 的方式完全不同。当您调用一个函数时,会为该调用创建一个词法环境,其中包含称为环境记录的东西。为了简单起见,我将它们一起称为“绑定对象”(不过,它们在规范中分开是有充分理由的;如果您想更深入地了解它,请留出几个小时并通读规范)。绑定对象包含函数参数的绑定、函数中声明的所有局部变量以及函数内声明的所有函数(以及其他一些东西)。一个绑定是名称(如a)和绑定的当前值的组合(以及我们在这里不需要担心的几个标志)。函数内的非限定引用(例如,fooin foo,但不是fooin obj.foo,它是限定的)首先针对绑定对象进行检查,以查看它是否与绑定对象匹配;如果是,则使用该绑定。当闭包在函数返回后幸存下来(这可能有多种原因),该函数调用的绑定对象会保留在内存中,因为闭包在创建它的地方有一个对绑定对象的引用。因此,在规范方面,一切都与对象有关。
At first glance, that would suggest that the stack isn't used for local variables; in fact, modern JavaScript engines are quite smart, and may (if it's worthwhile) use the stack for locals that aren't actually used by the closure. They may even use the stack for locals that doget used by the closure, but then move them into an binding object when the function returns so the closure continues to have access to them. (Naturally, the stack is still used for keeping track of return addresses and such.)
乍一看,这表明堆栈不用于局部变量;事实上,现代 JavaScript 引擎非常聪明,并且可能(如果值得的话)将堆栈用于闭包实际上并未使用的局部变量。他们甚至可以将堆栈用于确实被闭包使用的局部变量,但是当函数返回时将它们移动到绑定对象中,以便闭包继续访问它们。(当然,堆栈仍然用于跟踪返回地址等。)
Here's an example:
下面是一个例子:
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"
When we call foo, a binding object gets created with these bindings (according to the spec):
当我们调用 时foo,将使用这些绑定创建一个绑定对象(根据规范):
aandb— the arguments to the functionc— a local variable declared in the functionbar— a function declared within the function- (...and a couple of other things)
a和b——函数的参数c— 在函数中声明的局部变量bar— 在函数内声明的函数- (……还有其他几件事)
When fooexecutes the statement c = a + b;, it's referencing the c, a, and bbindings on the binding object for that call to foo. When fooreturns a reference to the barfunction declared inside it, barsurvives the call to fooreturning. Since barhas a (hidden) reference to the binding object for that specific call to foo, the binding object survives (whereas in the normal case, there would be no outstanding references to it and so it would be available for garbage collection).
当foo执行语句c = a + b;,它的引用c,a以及b该呼叫在绑定对象上绑定foo。当foo返回对其中bar声明的函数的引用时,bar在调用foo返回时仍然存在。由于bar对于该特定调用具有对绑定对象的(隐藏)引用foo,因此绑定对象仍然存在(而在正常情况下,不会有未完成的引用,因此可用于垃圾收集)。
Later, when we call bar, a newbinding object for that call is created with (amongst other things) a binding called d — the argument to bar. That new binding object gets a parentbinding object: The one attached to bar. Together they form a "scope chain". Unqualified references within barare first checked against the binding object for that call to bar, so for instance, dresolves to the dbinding on the binding object for the call to bar. But an unqualified reference that doesn't match a binding on that binding object is then then checked against its parent binding object in the scope chain, which is the binding object for the call to foothat created bar. Since that has a binding for c, that's the binding used for the identifier cwithin bar. E.g., in rough terms:
稍后,当我们调用 时bar,会为该调用创建一个新的绑定对象,其中包含(除其他外)一个名为 的绑定d — 的参数bar。该新绑定对象获得一个父绑定对象:附加到bar. 它们一起形成了一个“作用域链”。bar首先针对该调用的绑定对象检查其中的非限定引用bar,例如,d解析d为调用的绑定对象上的绑定bar。但是,随后会根据作用域链中的父绑定对象检查与该绑定对象上的绑定不匹配的非限定引用,该父绑定对象是对foo已创建对象的调用的绑定对象bar. 由于具有用于结合c,这是使用的绑定使标识符c内bar。例如,粗略地说:
+???????????????????????????+
| global binding object |
+???????????????????????????+
| .... |
+???????????????????????????+
^
| chain
|
+???????????????????????????+
| `foo` call binding object |
+???????????????????????????+
| a = 1 |
| b = 2 |
| c = 3 |
| bar = (function) |
+???????????????????????????+
^
| chain
|
+???????????????????????????+
| `bar` call binding object |
+???????????????????????????+
| d = 3 |
+???????????????????????????+
Fun fact: This scope chain is how global variables work in JavaScript. Note the "global binding object" in the above. So in a function, if you use an identifier that isn't in the binding object for that function call, and isn't in any of the other binding objects between that and the global binding object, if the global binding object has a binding for it, the global binding is used. Voilà, global variables. (ES2015 made this a bit more interesting by having two layers to the global binding object: A layer used by old-fashioned global declarations like varand function declarations, and a layer used by newer ones like let, const, and class. The difference is that the older layer also creates properties on the global object, which you kind of access via windowon browsers, but the newer layer doesn't. So a global letdeclaration doesn't create a windowproperty, but a global vardeclaration does.)
有趣的事实:这个作用域链是 JavaScript 中全局变量的工作原理。注意上面的“全局绑定对象”。因此,在函数中,如果您使用的标识符不在该函数调用的绑定对象中,并且不在该和全局绑定对象之间的任何其他绑定对象中,如果全局绑定对象具有绑定为此,使用全局绑定。瞧,全局变量。(ES2015使这个更有趣的一点通过具有两个层,以将全局绑定对象:通过像老式全局声明使用的层var和函数声明,并且通过等较新的使用的层let,const和class,不同的是,旧的层还在全局对象上创建属性,您可以通过window在浏览器上,但较新的层没有。所以全局let声明不会创建window属性,但全局var声明会。)
Implementations are free to use whatever mechanism they want under the covers to make the above seemto happen. It's impossible to get direct access to the binding object for a function call, and the spec makes clear that it's perfectly fine if the binding object is just a concept, rather than a literal part of the implementation. A simple implementation may well just literally do what the spec says; a more complicated one may use a stack when there are no closures involved (for the speed benefit), or may always use a stack but then "tear off" the binding object needed for a closure when popping the stack. The only way to know in any specific case is to look at their code. :-)
实现可以自由使用任何他们想要的机制在幕后作出上述似乎发生。无法直接访问函数调用的绑定对象,并且规范明确指出,如果绑定对象只是一个概念,而不是实现的文字部分,那就完全没问题了。一个简单的实现很可能只是按照规范所说的去做;一个更复杂的方法可能在不涉及闭包时使用堆栈(为了速度优势),或者可能总是使用堆栈,但在弹出堆栈时“撕掉”闭包所需的绑定对象。在任何特定情况下知道的唯一方法是查看他们的代码。:-)
More about closures, the scope chain, etc. here:
更多关于闭包、作用域链等的信息,请看这里:
- Closures are not complicated(somewhat out of date terminology)
- Poor misunderstood 'var'
- 闭包并不复杂(有些过时的术语)
- 可怜的被误解的“var”
回答by jJ'
Unfortunatelly the answer is: It depends.
不幸的是,答案是:视情况而定。
There was a big shift in recent javascript engines that started to optimize much better than they used to. The answer used to be: "Local variables are stored in heap-allocated stack frames for closures to work". It is not so simple anymore.
最近的 javascript 引擎发生了很大的变化,开始比以前更好地优化。过去的答案是:“局部变量存储在堆分配的堆栈帧中,以便闭包工作”。它不再那么简单了。
There has been (or used to be like 20-30 years ago) research for Scheme implementations and closure optimization (JavaScript inherited pretty much Scheme closures, except for continuations that make it even trickier).
已经(或曾经在 20 到 30 年前)研究过 Scheme 实现和闭包优化(JavaScript 继承了很多 Scheme 闭包,除了使其更加棘手的延续之外)。
I do not have the paper links ready, but if you do not have incredibly efficient garbage collector you need to use stack as well. The tricky part is then dealing with closures, which need to have variables heap-allocated. For that different strategies are used. The result is a hybrid where:
我没有准备好纸质链接,但是如果您没有非常高效的垃圾收集器,您也需要使用堆栈。棘手的部分是处理闭包,它需要堆分配变量。为此,使用了不同的策略。结果是一个混合体,其中:
- by inlining functions, you can reduce the number of heap-allocated frames being allocated/deallocated significantly
- some variables can be safely put on stack, since it's time-span is limited (it is often connected to inlining the function calls as well)
- in some cases you know that you might be creating closure, but you can wait until that happen and then allocate heap stack-frame for it and copy the current values from stack
- there are optimizations connected to tail-calls, where you can heap-allocate earlier and then reuse the stack frame for the next function call, but that is not used in javascript engines as far as I know currently
- 通过内联函数,您可以显着减少分配/解除分配的堆分配帧的数量
- 一些变量可以安全地放在堆栈上,因为它的时间跨度是有限的(它通常也与内联函数调用有关)
- 在某些情况下,您知道您可能正在创建闭包,但是您可以等到发生这种情况,然后为其分配堆堆栈帧并从堆栈中复制当前值
- 有与尾调用相关的优化,您可以在其中更早地进行堆分配,然后在下一个函数调用中重用堆栈帧,但据我所知,这在 javascript 引擎中没有使用
this field is changing really fast in several competing engines, so the answer will probably still be "it depends"
这个领域在几个竞争引擎中变化非常快,所以答案可能仍然是“这取决于”
In addition, in new versions of the language we will be seeing features like letand constthat actually make it easier for engines to optimize the allocation decisions. Especially immutability helps very much, since you can copy values freely off the stack (and make then part of the closure object for example) without resolving collisions of changing variables from different closures.
此外,在新版本的语言中,我们将看到类似这样的特性let,const这实际上使引擎更容易优化分配决策。特别是不变性非常有帮助,因为您可以从堆栈中自由复制值(例如,然后将其作为闭包对象的一部分),而无需解决来自不同闭包的更改变量的冲突。

