I wrote this code to teach myself about JavaScript closures:
function1 = function(){
var variable = "foo"
var function2 = function(argument){
console.log(variable + argument);
}
return function2
}
function3 = function1();
function3("bar");
This prints "foobar" as expected. But where does the variable live?
Does it become a property of function3, or stored somewhere else in function3? Does JavaScript traverse some kind of closure chain, similarly to how it traverses the prototype chain? Is it stored in memory somewhere else?
I am trying to understand this more deeply.
tl;dr:
where does the variable live?
In the environment it was defined in.
Does it become a property of function3, or stored somewhere else in function3?
No.
Does JavaScript traverse some kind of closure chain, similarly to how it traverses the prototype chain?
Yes.
Is it stored in memory somewhere else?
Yes.
tl;dr 2:
Functions keep a reference to the environment they are created in. When a function is called it creates a new environment whose parent is the environment the function kept the reference to.
Longer explanation:
Whenever a function is executed a new lexical environment is created. The environment has two "fields": an environment record where all the variables are being tracked and a outer lexical environment that refers to, as the name suggested, to the "parent lexical environment".
So when we your code example is evaluated, the initial state of the memory (before executing anything) might look like this (simplified):
+-(Global) lexical environment-+ +-Environment Record-+
+-------------+----------------+ +---------+----------+
| Environment | *--------+---> |function1|undefined |
| Record | | +---------+----------+
+-------------+----------------+ |function3|undefined |
| Outer | | +---------+----------+
| lexical | (empty) |
| environment | |
+-------------+----------------+
The global environment doesn't have any outer environment because it is at the top. function1
and function3
are two bindings that haven't been initialized yet (the assignment wasn't evaluated yet).
After creating the function (evaluating function1 = function() { ... }
), the memory looks like this:
+------------------------------------------------------------------------+
| |
v |
+-(Global) lexical environment-+ +-Environment Record-+ +-----Function Object-+---+
+-------------+----------------+ +---------+----------+ +---------------+-----+---+
| Environment | *--------+--->|function1| *-----+---->|[[Environment]]| * |
| Record | | +---------+----------+ +---------------+---------+
+-------------+----------------+ |function3|undefined | | name |function1|
| Outer | | +---------+----------+ +---------------+---------+
| lexical | (empty) |
| environment | |
+-------------+----------------+
Now function1
has a value, a function object. Function objects have multiple internal (e.g. [[Environment]]
) and external (e.g. name
) properties. As the name implies, internal properties cannot be accessed from user code. The [[Environment]]
property is very important. Notice how it refers back to the lexical environment the function was created in!
The next step is executing function3 = function1()
, i.e. calling function2
. As I said at the very beginning, whenever a function is executed a new lexical environment is created. Let's look at the memory just after entering the function:
+------------------------------------------------------------------------+
| |
v |
+-(Global) lexical environment-+ +-Environment Record-+ +-----Function Object-+---+
+-------------+----------------+ +---------+----------+ +---------------+-----+---+
| Environment | *--------+--->|function1| +---->|[[Environment]]| * |
| Record | | +---------+----------+ +---------------+---------+
+> +-------------+----------------+ |function3|undefined | | name |function1|
| | Outer | | +---------+----------+ +---------------+---------+
| | lexical | (empty) |
| | environment | |
| +-------------+----------------+
|
|
|
| +-----lexical environment------+ +-Environment Record-+
| +-------------+----------------+ +---------+----------+
| | Environment | *--------+--->|variable |undefined |
| | Record | | +---------+----------+
| +-------------+----------------+ |function2|undefined |
| | Outer | | +---------+----------+
| | lexical | * |
| | environment | | |
| +-------------+--------+-------+
| |
+-------------------------+
This looks very similar to the structure of the global environment! We have a lexical environment that has an environment record with two unintialized bindings. But the big difference now is that "outer lexical environment" points to the global lexical environment. How is that possible?
When calling function1
and creating a new lexical environment, we set the value of the new environments "outer lexical environment" field to the value of function1
's [[Environment]]
field. This is were the scope chain is created.
Now, after executing function1
, the memory has this structure:
+------------------------------------------------------------------------+
| |
v |
+-(Global) lexical environment-+ +-Environment Record-+ +-----Function Object-+---+
+-------------+----------------+ +---------+----------+ +---------------+-----+---+
| Environment | *--------+--->|function1| *-----+---->|[[Environment]]| * |
| Record | | +---------+----------+ +---------------+---------+
+> +-------------+----------------+ |function3| | | | name |function1|
| | Outer | | +---------+---+------+ +---------------+---------+
| | lexical | (empty) | |
| | environment | | |
| +-------------+----------------+ +-------------------------+
| |
| +----------------------------------------------------------------+--------+
| v | |
| +-----lexical environment------+ +-Environment Record-+ v |
| +-------------+----------------+ +---------+----------+ |
| | Environment | *--------+--->|variable | 'foo' | +-----Function Object-+---+
| | Record | | +---------+----------+ +---------------+-----+---+
| +-------------+----------------+ |function2| *-----+---->|[[Environment]]| * |
| | Outer | | +---------+----------+ +---------------+---------+
| | lexical | * | | name |function2|
| | environment | | | +---------------+---------+
| +-------------+--------+-------+
| |
+-------------------------+
Similar like function1
, function2
has a reference to the environment created by calling function2
. In addition, function3
refers to the function we created because we return it from function1
.
Last step: calling function3('bar')
:
+------------------------------------------------------------------------+
| |
v |
+-(Global) lexical environment-+ +-Environment Record-+ +-----Function Object-+---+
+-------------+----------------+ +---------+----------+ +---------------+-----+---+
| Environment | *--------+--->|function1| *-----+---->|[[Environment]]| * |
| Record | | +---------+----------+ +---------------+---------+
+> +-------------+----------------+ |function3| | | | name |function1|
| | Outer | | +---------+---+------+ +---------------+---------+
| | lexical | (empty) | |
| | environment | | |
| +-------------+----------------+ +-------------------------+
| |
| +----------------------------------------------------------------+--------+
| v | |
| +-----lexical environment------+ +-Environment Record-+ v |
| +-------------+----------------+ +---------+----------+ |
| | Environment | *--------+--->|variable | 'foo' | +-----Function Object-+---+
| | Record | | +---------+----------+ +---------------+-----+---+
|+>+-------------+----------------+ |function2| *-----+---->|[[Environment]]| * |
|| | Outer | | +---------+----------+ +---------------+---------+
|| | lexical | * | | name |function2|
|| | environment | | | +---------------+---------+
|| +-------------+--------+-------+
++------------------------+
|
| +-----lexical environment------+ +-Environment Record-+
| +-------------+----------------+ +---------+----------+
| | Environment | *--------+--->|argument | 'bar' |
| | Record | | +---------+----------+
| +-------------+----------------+
| | Outer | |
| | lexical | * |
| | environment | | |
| +-------------+--------+-------+
+------------------------+
Similar here, a new environment is created and its "outer lexical environment" field points to the environment created when function1
was called.
Now, looking up the value of argument
is straightforward, because it exists in the environment's own record. But when looking up variable
, the following happens: Since it doesn't exist in the environment's own record, it looks at its "outer lexical environment"'s record. It can do that because it has a reference to it.
Whenever JavaScript executes the function3 function, a 'scope' object is created to hold the local variable named by you as a variable ("foo"). Note that your JavaScript code cannot directly access this scope object. And thus the value "foo" is available to the inner function, though the outer function has returned.
Does JavaScript traverse some kind of closure chain, similarly to how it traverses the prototype chain?
Yes. "Scope objects form a chain called the scope chain, similar to the prototype chain used by JavaScript's object system.
A closure is the combination of a function and the scope object in which it was created. Closures let you save state — as such, they can often be used in place of objects"
Read more here:
- A re-introduction to JavaScript (JS tutorial)
- How do JavaScript closures work?
Variables live in the scope where they are declared, which is either global or a function.
The keyword here is scope.
As explained brilliantly in the MSDN website:
A variable that is declared inside a function definition is local. It
is created and destroyed every time the function is executed, and it
cannot be accessed by any code outside the function. JavaScript does
not support block scope (in which a set of braces {. . .} defines a
new scope), except in the special case of block-scoped variables.
EDIT:
It is actually a little more complicated than this, see toddmotto's post about JS scopes.
The closure is decently explained on the MDN website. But I would recommend going through this awesome explanation in a Stack Overflow answer, a precise answer to your question.
It is not the property. Just that the environment is remembered in memory. Environment means function and the scope and scope members. I hope it enlightens the path.
For an explanation on how closures work see this answer.
How do JavaScript closures work?
At the virtual machine level, every function has its own lexical environment which keeps track of this information. The virtual machine finds which variables are accessed in closures and stores them on the heap, and they live for as long as any closure might need them.
For more in-depth information see for example these two great pieces: