views:

158

answers:

5

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?

+1  A: 

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.

Delan Azabani
but what if foo is never invoked, then the closure bar was never created?
動靜能量
Exactly. That's right.
Delan Azabani
s/closure/function/g
Alsciende
+1  A: 

John Resig's new book, Secrets of a JS Ninja, has a really good run down of closures - he also goes through a lot of it on his site and it's well worth a read - I'd be surprised if you didn't learn something.

Martyn
+1  A: 

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:

  1. Case 1 is not a closure, it's just a function
  2. Case 2 is not a closure, it's another function with a free variable
  3. 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 of this. Otherwise, the value of this is the global object.
  4. Case 4 is not a closure, it's a function defined inside another function. Should foo return bar, you would create a closure that contains only 'bar' and its value : function bar() {}.
Alsciende
now, consider that the program calls moo.f, and moo.f does a "this.g = foo" and then "this.g()". Isn't it true that foo is a function and carries with it the context of the global scope? Then it fits in the definition of a closure?
動靜能量
A function can not be bound to the context of the global scope, since you can never return from the global scope. Thus it is never closed, therefore you can't have a closure on it.
Alsciende
you need to return it for it to be a closure? i don't think you have to return it. you can assign it and why does it matter? and i think the definition of closure: some code to run together with its context, in this case, the global scope, will fit the cases above all together.
動靜能量
Though, in the second example, were I to call foo in an environment where i has a completely different meaning. It would still refer to the i in the global scope. Even if I called it in a function that shadowed the i with some other i.
Lajla
A: 

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
Lajla
A: 

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.

Chris Henry