When actually is a closure created?
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 globa开发者_如何学Cl 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?
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).
<script type="text/javascript">
var i = 1;
function foo() { return i; }
</script>
Here, i
is a free variable for the function code of foo
. And this free variable is not bound to any particular value by any existing context (closure). So you don't have any closure.
<script type="text/javascript">
var i = 1;
function foo() { return i; }
foo(); // returns 1
i = 2;
foo(); // returns 2
</script>
Now to create a closure you have to provide a value-bounding context:
<script type="text/javascript">
function bar() {
var i = 1;
function foo() { return i; }
return foo;
}
bar(); // returns function foo() { return i; }
bar()(); // returns 1
// no way to change the value of the free variable i => bound => closure
</script>
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.
<script type="text/javascript">
function bar() {
var i = 1;
function foo() { return i; }
i = 2;
return foo;
}
bar()(); // returns 2
</script>
Concerning your exemples:
- Case 1 is not a closure, it's just a function
- Case 2 is not a closure, it's another function with a free variable
- Case 3 is not a closure, it's yet another function with the special "variable"
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. - Case 4 is not a closure, it's a function defined inside another function. Should
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:
function createAdder(x) { //this is not a closure
return function(y) { //this function is the closure however, it closes over the x.
return y + x;
}
} //thus createAdder returns a closure, it's closed over the argument we put into createAdder
var addTwo = createAdder(2);
addTwo(3); //3
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.
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,
function foo() { }
When the JS completes running the above line, there is a closure already, or
var fn = function() { };
Or
return function() { return 1; };
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, but bar()
doesn't exist yet (without the calling of foo()
), so there was one closure created, not two.
精彩评论