If I may offer a model on when and how closures are created (this discussion is theoretical, in reality the interpreter may do anything as long as the end result is the same): a closure is created whenever a function is evaluated during execution. The closure will then point to the environment where the execution happens. When a site load, the Javascript is executed in the order from top to bottom at the global environment. All occurrences of
function f(<vars>) {
<body>
}
will be turned into a closure with and , with a pointer to a global environment. At the same time, a reference f
is made at the global environment pointing to this closure.
So what happened when f()
is executed at global environment? We can think of it as, first, a lookup in the global environment (where the function is executing) for the name f
. We found that it is pointing to a closure. To execute the closure, we create a new environment, whose parent environment is the environment being pointed by the closure f
, i.e. the global environment. In this new environment, we associate the arguments of f
with its real values. Then the body of the closure f
is executed in the new environment! Any variable f
needs will be resolved first in the new environment we just created. If such variable does not exist, we recursively find it in the parent environment until we hit the global environment. Any variable f
creates will be created in the new environment.
Now, let's look at the more complicated example:
// At global level
var i = 10; // (1)
function make_counter(start) {
return function() {
var value = start++;
return value;
};
} // (2)
var count = make_counter(10); // (3)
count(); // return 10 // (4)
count(); // return 11 // (5)
count = 0; // (6)
What happens is that:
At point (1): an association from i
to 10
is made at global environment (where var i = 10;
is executed.
At point (2): a closure is made with variable (start)
and body return ...;
that points to the environment where it is being executed (the global). Then an association is made from make_counter
to the closure we just created.
At point (3): several interesting things happen. First we find what make_counter
is associated with at the global environment. Then we execute that closure. Hence, a new environment, let's name it CE
is created which points to the environment pointed by closure make_counter
(the global). Then we create an association from start
to 10
in CE
and run the body of closure make_counter
in CE
. Here we encounter another function, which is anonymous. However, what happens is the same as before (recall function f() {}
is equivalent to var f = function() {};
). A closure, let's name it count
, is created with variable ()
(empty list) and body var ... return value;
. Now, this closure will point to the environment where it is executing, i.e. CE
. This will be very important later on. Finally, we have count
points to the new closure in the global environment (Why global? Because var count ...
is executed at the global environment). We note that CE
is not garbage-collected because we can reach CE
through the closure make_counter
, which we can reach from the global environment from the variable make_counter
.
At point (4), more interesting thing happens. We first find the closure associated with count
which is the closure we just created. Then we create a new environment whose parent is the environment pointed by the closure, which is CE
! We execute the body of the closure in this new environment. When var value = start++;
is executed, we search for variable start
beginning at the current environment and moving up all the way to the global environment sequentially. We found start
in environment CE
. We increment the value of this start
, originally 10
to 11
. Now the start
in CE
points to value 11
. When we encounter var value
, this means don't bother looking for an existing value
and simply create a variable at the environment where it is being executed. So an association from value
to 11
is made. In the return value;
, we lookup value
the same way as we looked for start
. Turns out we find it at the current environment, hence we don't need to look through the parent environments. We then return this value. Now the new environment we just created will be garbage collected as we can no longer reach this environment through any path from global.
At point (5), the same thing happens as above. But now, when we look for start
, we found that the value is 11
instead of 10
(at the environment CE
).
At point (6), we re-assign count
at the global environment. We found that now we can no longer find a path from global to closure count
and in turn we can no longer found a path to environment CE
. Hence both of these will be garbage collected.
P.S. For those familiar with LISP or Scheme, the model above is exactly the same as the environment model in LISP/Scheme.
P.P.S. Wow, at first I wanted to write a short answer, but it turns out to be this behemoths. I hope I'm not making glaring mistake.