Is it true that a closure is created in the following cases for foo
, but not for bar
?
Case 1:
<script type="text/javascript">
function foo() { }
</script>
foo
is a closure with a scope chain with only the global scope.
Case 2:
<script type="text/javascript">
var i = 1;
function foo() { return i; }
</script>
same as Case 1.
Case 3:
<script type="text/javascript">
function Circle(r) {
this.r = r;
}
Circle.prototype.foo = function() { return 3.1415 * this.r * this.r }
</script>
in this case, Circle.prototype.foo
(which returns the circle's area) refers to a closure with only the global scope. (this closure is created).
Case 4:
<script type="text/javascript">
function foo() {
function bar() {
}
}
</script>
here, foo
is a closure with only the global scope, but bar
is not a closure (yet), because the function foo
is not invoked in the code, so no closure bar
is ever created. It will only exist if foo
is invoked , and the closure bar
will exist until foo
returns, and the closure bar
will then be garbage collected, since there is no reference to it at all anywhere.
So when the function doesn't exist, can't be invoked, can't be referenced, then the closure doesn't exist yet (never created yet). Only when the function can be invoked or can be referenced, then the closure is actually created?
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
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 namef
. 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 closuref
, i.e. the global environment. In this new environment, we associate the arguments off
with its real values. Then the body of the closuref
is executed in the new environment! Any variablef
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 variablef
creates will be created in the new environment.Now, let's look at the more complicated example:
What happens is that:
At point (1): an association from
i
to10
is made at global environment (wherevar i = 10;
is executed.At point (2): a closure is made with variable
(start)
and bodyreturn ...;
that points to the environment where it is being executed (the global). Then an association is made frommake_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 itCE
is created which points to the environment pointed by closuremake_counter
(the global). Then we create an association fromstart
to10
inCE
and run the body of closuremake_counter
inCE
. Here we encounter another function, which is anonymous. However, what happens is the same as before (recallfunction f() {}
is equivalent tovar f = function() {};
). A closure, let's name itcount
, is created with variable()
(empty list) and bodyvar ... 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 havecount
points to the new closure in the global environment (Why global? Becausevar count ...
is executed at the global environment). We note thatCE
is not garbage-collected because we can reachCE
through the closuremake_counter
, which we can reach from the global environment from the variablemake_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 isCE
! We execute the body of the closure in this new environment. Whenvar value = start++;
is executed, we search for variablestart
beginning at the current environment and moving up all the way to the global environment sequentially. We foundstart
in environmentCE
. We increment the value of thisstart
, originally10
to11
. Now thestart
inCE
points to value11
. When we encountervar value
, this means don't bother looking for an existingvalue
and simply create a variable at the environment where it is being executed. So an association fromvalue
to11
is made. In thereturn value;
, we lookupvalue
the same way as we looked forstart
. 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 is11
instead of10
(at the environmentCE
).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 closurecount
and in turn we can no longer found a path to environmentCE
. 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.
Actually, after several more years of JavaScript use and fairly thorough studies of it, I now have a better answer:
Whenever a function comes into existence, then a closure is created.
Because a function is just an object, we can more precisely say, whenever a Function object is instantiated (the function instance comes into existence), a closure is created.
So,
When the JS completes running the above line, there is a closure already, or
Or
Why? Because a closure is just a function with a scope chain, so in every situation above, a function existed (it came into existence. You can call it (invoke it)). It also had a scope. So in my original question (I was the OP), every Case 1 to 4, there was a closure created, in every single case.
Case 4 is an interesting case. After that code is run, there is a closure due to
foo()
coming into existence, butbar()
doesn't exist yet (without the calling offoo()
), so there was one closure created, not two.A closure is when free variables in some function code are bound to some values by the function "context" (closure being a more proper term here than context).
Here,
i
is a free variable for the function code offoo
. And this free variable is not bound to any particular value by any existing context (closure). So you don't have any closure.Now to create a closure you have to provide a value-bounding context:
In summary, you can't have a closure unless a function returns another function. In that case, the returned function has all the variable-value bindings that existed in the returning function when it exited.
Concerning your exemples:
this
. When the function is called as member of an object, the object is assigned to the value ofthis
. Otherwise, the value ofthis
is the global object.foo
returnbar
, you would create a closure that contains only 'bar' and its value :function bar() {}
.closure bar will exist until foo returns, and the closure bar will then be garbage collected, since there is no reference to it at all anywhere
Yes.
In none of these examples is a closure created.
The second would create a closure if you actually created a function and did something with it, now you just create a function and then throw it away. Same as adding a line
3+8;
, you create a number, and then throw it away.A closure is simply a function which references variables from its creation environment in its body, a canonical example is an adder: