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.
So. With this in mind, the answer is that variables in a closure are stored in the stack and heap.
A Closure is a combination of a function enclosed with references to its surrounding state (the lexical environment). In JavaScript, closures are created every time a function is created at run time. In other words, a closure is just a fancy name for a function that remembers the external things used inside it.
JavaScript allows us to nest scopes, and variables declared in outer scopes are accessible from all inner ones. Variables can be globally-, module-, or block-scoped. A closure is a function enclosed with references to the variables in its outer scope.
A closure is created every time an enclosing outer function is called. In other words, the inner function does not need to return for a closure to be created. The scope of a closure in JavaScript is lexical, meaning it's defined statically by its location within the source code.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With