How do JavaScript closures work under the hood

“When any JavaScript code is executing, it needs some place to store its local variables. Let’s call this place as a scope object (some refer to it as a LexicalEnvironment). For example, when you invoke some function, and function defines local variables, these variables are saved on the scope object. You can think of it as a regular JavaScript object, with the notable difference that you can’t refer to the whole object directly. You can only modify its properties, but you can’t refer to the scope object itself.

This concept of scope object is very different from, say, C or C++, where local variables are stored on stack. In JavaScript, scope objects are allocated in heap instead (or at least they behave like this), so they might stay allocated even if function already returned. More on that later.

As you might expect, scope object might have parent. When the code tries to access some variable, interpreter looks for the property of current scope object. If the property doesn’t exist, interpreter moves to the parent scope object, and looks there. And so on, until the value is found, or there’s no more parent. Let’s call this sequence of scope objects as a scope chain.

The behavior of resolving a variable on scope chain is very similar to that of prototypal inheritance, with, again, one notable difference: if you try to access some non-existing property of regular object, and prototype chain doesn’t contain this property either, it’s not an error: undefined is silently returned. But if you try to access non-existing property on the scope chain (i.e. access non-existing variable), then ReferenceError occurs.

The last element in the scope chain is always the Global Object. In top-level JavaScript code, scope chain contains just a single element: the Global Object. So, when you define some variables in top-level code, they are defined on Global Object. When some function is invoked, scope chain consists of more than one object. You might expect that if function is called from top-level code, then scope chain is guaranteed to contain exactly 2 scope objects, but this is not necessarily true! There might be 2 or more scope objects; it depends on the function. More on that later…”