When actually is a closure created?

后端 未结 5 857
谎友^
谎友^ 2021-02-04 08:37

Is it true that a closure is created in the following cases for foo, but not for bar?

Case 1:



        
相关标签:
5条回答
  • 2021-02-04 08:53

    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.

    0 讨论(0)
  • 2021-02-04 09:09

    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() {}.
    0 讨论(0)
  • 2021-02-04 09:12

    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.

    0 讨论(0)
  • 2021-02-04 09:17

    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
    
    0 讨论(0)
  • 2021-02-04 09:18

    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.

    0 讨论(0)
提交回复
热议问题