Javascript中变量是如何分配内存的? [英] How variables are allocated memory in Javascript?

查看:22
本文介绍了Javascript中变量是如何分配内存的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

解决方案

其实是 JavaScript 一个非常有趣的领域,至少有两个答案:

  • 关于规范定义的答案,以及
  • 关于 JavaScript 引擎实际做什么的答案,可以优化(并且经常是)

就规范而言:JavaScript 处理局部变量的方式与 C 的处理方式大不相同.当您调用函数时,除其他外,词法环境 为该调用创建了一个名为 环境记录.为了简单起见,我将它们一起称为绑定对象"(不过,它们在规范中分开是有充分理由的;如果您想更深入地了解它,请留出几个小时并通读规范).绑定对象包含函数参数的绑定、函数中声明的所有局部变量以及函数中声明的所有函数(以及其他一些东西).binding 是名称(如 a)和绑定的当前值的组合(以及我们在这里不需要担心的几个标志).函数内的非限定引用(例如,foo 中的 foo,而不是 obj.foo 中的 foo,这是合格的)首先检查绑定对象,看它是否与绑定对象匹配;如果是,则使用该绑定.当闭包在函数返回后幸存下来(这可能有多种原因),该函数调用的绑定对象保留在内存中,因为闭包在创建它的地方有一个对绑定对象的引用.因此,在规范方面,一切都与对象有关.

乍一看,这表明堆栈不用于局部变量;事实上,现代 JavaScript 引擎非常聪明,并且可能(如果值得的话)将堆栈用于闭包实际上并未使用的局部变量.他们甚至可以将堆栈用于确实被闭包使用的局部变量,但是当函数返回时将它们移动到一个绑定对象中,这样闭包就可以继续访问它们.(当然,堆栈仍然用于跟踪返回地址等.)

这是一个例子:

function foo(a, b) {变量 c;c = a + b;功能栏(d){alert("d * c = " + (d * c));}返回栏;}var b = foo(1, 2);乙(3);//提示d * c = 9"

当我们调用 foo 时,会使用这些绑定创建一个绑定对象(根据规范):

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

foo 执行语句 c = a + b; 时,它引用了 c, a,和 b 在绑定对象上绑定,用于调用 foo.当 foo 返回对其中声明的 bar 函数的引用时,bar 在对 foo 返回的调用中存活下来.由于 barfoo 的特定调用具有对绑定对象的(隐藏)引用,因此绑定对象仍然存在(而在正常情况下,不会有未完成的引用到它,所以它可以用于垃圾收集).

稍后,当我们调用 bar 时,会为该调用创建一个 new 绑定对象,其中包括一个名为 d 的绑定 —bar 的参数.这个新的绑定对象得到一个 parent 绑定对象:附加到 bar 的那个.它们一起形成了一个作用域链".bar 中的非限定引用首先针对 bar 调用的绑定对象进行检查,例如,d 解析为 d 在绑定对象上绑定以调用 bar.但是,随后会根据作用域链中的父绑定对象检查与该绑定对象上的绑定不匹配的非限定引用,该对象是创建 foo 调用的绑定对象>bar.由于它具有 c 的绑定,因此它是用于 bar 中的标识符 c 的绑定.例如,粗略地说:

<前>+−−−−−−−−−−−−−−−−−−−−−−−−−−−−+|全局绑定对象|+−−−−−−−−−−−−−−−−−−−−−−−−−−−−+|.... |+−−−−−−−−−−−−−−−−−−−−−−−−−−−−+^|链|+−−−−−−−−−−−−−−−−−−−−−−−−−−−−+|`foo` 调用绑定对象 |+−−−−−−−−−−−−−−−−−−−−−−−−−−−−+|一 = 1 ||b = 2 ||c = 3 ||bar = (函数) |+−−−−−−−−−−−−−−−−−−−−−−−−−−−−+^|链|+−−−−−−−−−−−−−−−−−−−−−−−−−−−−+|`bar` 调用绑定对象|+−−−−−−−−−−−−−−−−−−−−−−−−−−−−+|d = 3 |+−−−−−−−−−−−−−−−−−−−−−−−−−−−−+

有趣的事实:这个作用域链是全局变量在 JavaScript 中的工作方式.注意上面的全局绑定对象".因此,在函数中,如果您使用的标识符不存在于该函数调用的绑定对象中,并且不存在于该函数与全局绑定对象之间的任何其他绑定对象中,如果全局绑定对象具有绑定为此,使用全局绑定.瞧,全局变量.(ES2015 通过为全局绑定对象设置两层使这更有趣一些:老式全局声明(如 var 和函数声明)使用的层,以及 等较新的全局声明使用的层>letconstclass.不同之处在于旧层还在全局对象上创建属性,您可以通过 访问这些属性window 在浏览器上,但较新的层没有.所以全局 let 声明不会创建 window 属性,而是创建全局 var 声明确实如此.)

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

这里有更多关于闭包、作用域链等的信息:

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?

解决方案

It's actually a very interesting area of JavaScript, and there are at least two answers:

  • An answer in terms of what the specification defines, and
  • An answer in terms of what JavaScript engines actually do, which may be optimized (and often is)

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 environment for 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 bindings for 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 binding is 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 foo in foo, but not the foo in 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 retained in 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.

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 do get 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.)

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):

  • a and b — the arguments to the function
  • c — a local variable declared in the function
  • bar — a function declared within the function
  • (...and a couple of other things)

When foo executes the statement c = a + b;, it's referencing the c, a, and b bindings on the binding object for that call to foo. When foo returns a reference to the bar function declared inside it, bar survives the call to foo returning. Since bar has 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).

Later, when we call bar, a new binding object for that call is created with (amongst other things) a binding called d — the argument to bar. That new binding object gets a parent binding object: The one attached to bar. Together they form a "scope chain". Unqualified references within bar are first checked against the binding object for that call to bar, so for instance, d resolves to the d binding 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 foo that created bar. Since that has a binding for c, that's the binding used for the identifier c within bar. E.g., in rough terms:

+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
|   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 var and 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 window on browsers, but the newer layer doesn't. So a global let declaration doesn't create a window property, but a global var declaration does.)

Implementations are free to use whatever mechanism they want under the covers to make the above seem to 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:

这篇关于Javascript中变量是如何分配内存的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆