How variables are allocated memory in Javascript?

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?


Solution 1:

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:

  • Closures are not complicated (somewhat out of date terminology)
  • Poor misunderstood 'var'

Solution 2:

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.

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

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

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 let and const that 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.