What is a 'Closure'?

前端 未结 23 1138
星月不相逢
星月不相逢 2020-11-22 08:02

I asked a question about Currying and closures were mentioned. What is a closure? How does it relate to currying?

相关标签:
23条回答
  • Variable scope

    When you declare a local variable, that variable has a scope. Generally, local variables exist only within the block or function in which you declare them.

    function() {
      var a = 1;
      console.log(a); // works
    }    
    console.log(a); // fails
    

    If I try to access a local variable, most languages will look for it in the current scope, then up through the parent scopes until they reach the root scope.

    var a = 1;
    function() {
      console.log(a); // works
    }    
    console.log(a); // works
    

    When a block or function is done with, its local variables are no longer needed and are usually blown out of memory.

    This is how we normally expect things to work.

    A closure is a persistent local variable scope

    A closure is a persistent scope which holds on to local variables even after the code execution has moved out of that block. Languages which support closure (such as JavaScript, Swift, and Ruby) will allow you to keep a reference to a scope (including its parent scopes), even after the block in which those variables were declared has finished executing, provided you keep a reference to that block or function somewhere.

    The scope object and all its local variables are tied to the function and will persist as long as that function persists.

    This gives us function portability. We can expect any variables that were in scope when the function was first defined to still be in scope when we later call the function, even if we call the function in a completely different context.

    For example

    Here's a really simple example in JavaScript that illustrates the point:

    outer = function() {
      var a = 1;
      var inner = function() {
        console.log(a);
      }
      return inner; // this returns a function
    }
    
    var fnc = outer(); // execute outer to get inner 
    fnc();
    

    Here I have defined a function within a function. The inner function gains access to all the outer function's local variables, including a. The variable a is in scope for the inner function.

    Normally when a function exits, all its local variables are blown away. However, if we return the inner function and assign it to a variable fnc so that it persists after outer has exited, all of the variables that were in scope when inner was defined also persist. The variable a has been closed over -- it is within a closure.

    Note that the variable a is totally private to fnc. This is a way of creating private variables in a functional programming language such as JavaScript.

    As you might be able to guess, when I call fnc() it prints the value of a, which is "1".

    In a language without closure, the variable a would have been garbage collected and thrown away when the function outer exited. Calling fnc would have thrown an error because a no longer exists.

    In JavaScript, the variable a persists because the variable scope is created when the function is first declared and persists for as long as the function continues to exist.

    a belongs to the scope of outer. The scope of inner has a parent pointer to the scope of outer. fnc is a variable which points to inner. a persists as long as fnc persists. a is within the closure.

    0 讨论(0)
  • 2020-11-22 08:53

    To help facilitate understanding of closures it might be useful to examine how they might be implemented in a procedural language. This explanation will follow a simplistic implementation of closures in Scheme.

    To start, I must introduce the concept of a namespace. When you enter a command into a Scheme interpreter, it must evaluate the various symbols in the expression and obtain their value. Example:

    (define x 3)
    
    (define y 4)
    
    (+ x y) returns 7
    

    The define expressions store the value 3 in the spot for x and the value 4 in the spot for y. Then when we call (+ x y), the interpreter looks up the values in the namespace and is able to perform the operation and return 7.

    However, in Scheme there are expressions that allow you to temporarily override the value of a symbol. Here's an example:

    (define x 3)
    
    (define y 4)
    
    (let ((x 5))
       (+ x y)) returns 9
    
    x returns 3
    

    What the let keyword does is introduces a new namespace with x as the value 5. You will notice that it's still able to see that y is 4, making the sum returned to be 9. You can also see that once the expression has ended x is back to being 3. In this sense, x has been temporarily masked by the local value.

    Procedural and object-oriented languages have a similar concept. Whenever you declare a variable in a function that has the same name as a global variable you get the same effect.

    How would we implement this? A simple way is with a linked list - the head contains the new value and the tail contains the old namespace. When you need to look up a symbol, you start at the head and work your way down the tail.

    Now let's skip to the implementation of first-class functions for the moment. More or less, a function is a set of instructions to execute when the function is called culminating in the return value. When we read in a function, we can store these instructions behind the scenes and run them when the function is called.

    (define x 3)
    
    (define (plus-x y)
      (+ x y))
    
    (let ((x 5))
      (plus-x 4)) returns ?
    

    We define x to be 3 and plus-x to be its parameter, y, plus the value of x. Finally we call plus-x in an environment where x has been masked by a new x, this one valued 5. If we merely store the operation, (+ x y), for the function plus-x, since we're in the context of x being 5 the result returned would be 9. This is what's called dynamic scoping.

    However, Scheme, Common Lisp, and many other languages have what's called lexical scoping - in addition to storing the operation (+ x y) we also store the namespace at that particular point. That way, when we're looking up the values we can see that x, in this context, is really 3. This is a closure.

    (define x 3)
    
    (define (plus-x y)
      (+ x y))
    
    (let ((x 5))
      (plus-x 4)) returns 7
    

    In summary, we can use a linked list to store the state of the namespace at the time of function definition, allowing us to access variables from enclosing scopes, as well as providing us the ability to locally mask a variable without affecting the rest of the program.

    0 讨论(0)
  • 2020-11-22 08:53

    A closure is a stateful function that is returned by another function. It acts as a container to remember variables and parameters from its parent scope even if the parent function has finished executing. Consider this simple example.

      function sayHello() {
      const greeting = "Hello World";
    
      return function() { // anonymous function/nameless function
        console.log(greeting)
      }
    }
    
    
    const hello = sayHello(); // hello holds the returned function
    hello(); // -> Hello World
    

    Look! we have a function that returns a function! The returned function gets saved to a variable and invoked the line below.

    0 讨论(0)
  • 2020-11-22 08:54

    In a normal situation, variables are bound by scoping rule: Local variables work only within the defined function. Closure is a way of breaking this rule temporarily for convenience.

    def n_times(a_thing)
      return lambda{|n| a_thing * n}
    end
    

    in the above code, lambda(|n| a_thing * n} is the closure because a_thing is referred by the lambda (an anonymous function creator).

    Now, if you put the resulting anonymous function in a function variable.

    foo = n_times(4)
    

    foo will break the normal scoping rule and start using 4 internally.

    foo.call(3)
    

    returns 12.

    0 讨论(0)
  • 2020-11-22 08:54

    • A closure is a subprogram and the referencing environment where it was defined

    – The referencing environment is needed if the subprogram can be called from any arbitrary place in the program

    – A static-scoped language that does not permit nested subprograms doesn’t need closures

    – Closures are only needed if a subprogram can access variables in nesting scopes and it can be called from anywhere

    – To support closures, an implementation may need to provide unlimited extent to some variables (because a subprogram may access a nonlocal variable that is normally no longer alive)

    Example

    function makeAdder(x) {
    return function(y) {return x + y;}
    }
    var add10 = makeAdder(10);
    var add5 = makeAdder(5);
    document.write(″add 10 to 20: ″ + add10(20) +
    ″<br />″);
    document.write(″add 5 to 20: ″ + add5(20) +
    ″<br />″);
    
    0 讨论(0)
  • 2020-11-22 08:54

    From Lua.org:

    When a function is written enclosed in another function, it has full access to local variables from the enclosing function; this feature is called lexical scoping. Although that may sound obvious, it is not. Lexical scoping, plus first-class functions, is a powerful concept in a programming language, but few languages support that concept.

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