For people starting with JavaScript, closures can feel mysterious. You can read the same explanation several times and still not see what a closure is or how the mechanism works—and it is almost guaranteed to come up in an interview.
A loose definition helps: a closure is what emerges when a block of code and its context interact. Take this example:
function foo(){
var x = 1;
return function (){
alert(++x); //2
}
}
var bar = foo();
bar();
The first question is: which part is the closure—foo or the anonymous function?
How a closure comes into being
In JavaScript, a function can create a boundary for scope. When foo executes and is activated, it creates a dynamic scope. That scope contains the variable x and the anonymous function being returned; normally, both would be pushed onto the stack. When the function finishes, it would pop its internal variables and functions, leave that scope environment, and return to the outer context.
Here, however, the variable and function inside foo have a relationship with something outside that scope: bar references the return value of foo(). As a result, x and the anonymous function cannot be destroyed when foo finishes. That is the closure people talk about. The usual push/pop stack model no longer applies cleanly to this situation, because it conflicts with the structure of a stack. How such closure data is allocated in memory comes later.
The important point is that the closure is neither the foo function nor the anonymous function by itself. It is the result of the variable x, the anonymous function, and the surrounding context existing together.
Two conditions are involved:
- The context that created the closure has not destroyed it.
- The function references a free variable—one that is not defined in the function block and is not passed in through
arguments. In the anonymous function above,xis a free variable.
Consider another example:
var x = 1;
function foo(){
alert(x);
}
~function(){
var x = 2;
foo(); //1
}();
Why does this alert 1? First, the following three forms have the same effect, even though they are parsed differently (A and C belong to one family; B belongs to another):
~function(){
var x = 2;
foo();
}(); //A
(function(){
var x = 2;
foo();
}()); //B
(function(){
var x = 2;
foo();
})(); //C
Why the lookup reaches 1
This closure is not quite the same as the first one. In the first case, something inside the scope was kept from being destroyed because it was tied to an outside reference. Here, nothing needs to be kept alive in that way, but foo is still a closure.
Inside foo, x is a free variable. When the closure for foo is created, its context is saved. While foo is active, it first looks in its Activation Object—the place for variables and functions declared inside foo that are not free variables. If the needed object is not found there, it looks in the saved context. From there it can continue through the outer scope chain. Because foo was created in the global scope, the x it reaches is the global x = 1. The x = 2 inside the immediately invoked function is not part of the scope chain that foo captured when it was created.
How closure memory is allocated
Return to the question of memory allocation. If closure memory were implemented with a stack structure, entering foo would push x from the surrounding scope; the lookup would climb to var x = 2 and produce alert(2). That is not what happens. The closure data from the outer scope is dynamically allocated memory, kept on the heap. The parser records how many times that closure data is referenced. When the reference count reaches zero, the garbage collector can handle it.
How a closure can hold on to memory
IE has often had trouble with high memory use when closures are present. In the first example, a reference cycle forms:
window <=> foo <=> anonymous function <=> bar <=> window
Even after:
bar = null;
the anonymous function still has a reference count greater than zero. Note that deleting a variable does not delete the object it references; it breaks the reference, which reduces that object's reference count by one. As a result, this closure can remain stuck in memory, holding onto its allocation.
Prototype chains, closures, and scope chains are topics where definitions only go so far. Understanding them takes repeated experiments and hands-on attempts. This walkthrough of closures is one attempt to make the mechanism clearer; there are surely places where it can be refined or corrected.