2
头图

Closures are a common concept in JavaScript, but various other languages also simulate the behavior of closures, including Java, C++, Objective-C, C#, Golang, etc. (but they are still different from traditional closures) . I have not been very clear about this concept before. I hope to master the principles and application scenarios of closures by reading network materials and learning through them.

Definition and original intention

According to the source of Wikipedia, the concept of closure (closure) was first defined by Peter Landin in 1964 to express the "environment part" + "control part" when solving expressions on his SECD machine. This term is used to Refers to a lambda expression [1] for which some open binding (free variable) has been closed (close, or bound) by its surrounding lexical environment. This concept is still somewhat abstract, and a more explicit expression is the MDN Web community's description of closures: a function is bundled with a reference to its surrounding state (lexical environment), such a combination is called a closure [2]. Further understanding everyone also has different views [3].

By definition, the biggest use of a closure is to bundle a function with a set of its "private" variables. In the process of this function being called multiple times, these variables can remain changed, and will not be changed by other functions. It is not difficult to retain the value of a variable to be used many times. The core value is to ensure the privacy of this variable and hide relevant information from the outside world. This involves the scope of variables, which is also a major knowledge point in JavaScript. Most of the material also applies and interprets closures in JS.

Closure examples and applications

The official website tutorial gives a clear example to illustrate the definition of closure:

 function makeFunc() {
    var name = "Mozilla";
    function displayName() {
        alert(name);
    }
    return displayName;
}
 
var myFunc = makeFunc();
myFunc();

First, this code works. Intuitively, var myFunc = makeFunc() has finished running the makeFunc() function, but the reason why subsequent calls to myFunc() can be processed normally is that a closure is formed. The function that forms the closure here is the displayName instance, and the variable bound to it is the name. To be precise, the variable name is stored in the lexical environment of the displayName function instance, which together form the closure. Therefore, when calling myFunc, the variable name can still be alerted.

This is an example to illustrate the concept. In real application scenarios, closures are used in many ways. Here are some collected cases:

JavaScript simulates private methods with closures

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures#emulating_private_methods_with_closures

 var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
})();
 
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */

Uniquely, this approach can be used to implement scenarios similar to Java encapsulating private functions, where multiple functions can share the same lexical environment (operate on the same private variables)

Use of Golang closures and coroutines

The example that brought me to re-understand the closure is a question answered in segmentfault when learning golang ( https://segmentfault.com/q/1010000041722259 ), the coroutine usage in the following code:

 for i := 0; i < 100; i++ {
    go func(i int) {
        fmt.Println(i)
    }(i)
}

And a similar example from the Golang study site: https://books.studygolang.com/the-way-to-go_EN_CN/16.9.html

In Golang, a closure is embodied as a nested anonymous function, and its main uses include [8]:

  1. Like JS, function private variables extend the life cycle of variables and isolate variables from outside access.
  2. Callbacks, as in other languages.
  3. Wrap functions, and make "middleware" (middleware, Golang's concept of reusable functions). In Golang, functions are first-class citizens, you can put functions as parameters into another function, then some common processing logic, such as printing logs, timers, etc., can be implemented as closures.
  4. In many library functions, you can use closures to pass in functions to take full advantage of the convenience brought by library functions. For example, in the sort package, you can use the functions in the closure to determine the filter conditions for search objects.

Closures and Lambda Functions in Java

After Java 8, the language ecology no longer only focuses on objects. In many cases, the method of functional programming is more concise and lightweight, so Lambda expressions are introduced. And it is often mentioned together with "anonymous functions" and "closures". First of all, it needs to be clear that a Lambda function and an anonymous function are basically equal, but not equivalent to a closure. It can be seen from the origin that a closure is a concept introduced when calculating a Lambda expression, not equal to the Lambda expression itself. . For the interpretation of the difference between Lambda expressions and closures, please refer to [10], which has a very detailed description. In short, Lambda expression is a concise way to write programs, and it involves open expressions - when the variables in the expression are outside the function, you need to use the closure method to lexicalize the function and the external parameters. Environments are bound to perform computations. This is true for any language.

 int n = 0;
final int k = n; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
    int i = k;
    // do something
};
n++;      // Now will not generate an error
r.run();  // Will run with i = 0 because k was 0 when the lambda was created

The variable k is beyond the Lambda expression and needs to be introduced using a closure. It is worth noting that the best way to write Java itself is to encapsulate an object containing private variables and private functions, and it does not need to be simulated like JavaScript.

Closure implementation

According to the definition of the closure and the desired functional performance, we can see that the common practice behind the closure is to use a data structure. In this structure, a pointer to the function code needs to be saved first, and then the creation of the closure needs to be saved. The lexical environment at the time of creation, most typically to save all variables available at the time of creation.

In an ideal language that can implement closures, in the runtime memory model, all atomic variables should be placed on a linear stack. In this language scenario, if a closure is created, the variables in the corresponding lexical environment cannot be recycled after the function is executed. The typical approach at this time is to put the variables in the heap (which can be used with the closure in the Java example). The variables need to be combined with final modification) until all closure references are used up before recycling. I think this also indirectly explains the origin of the "memory leak when using closures in IE" that is widely circulated on the Internet. Closures are also more suitable for languages that do "garbage collection".

For languages that only operate on the stack, it is not so easy to implement closures, and these atomic development variables will have problems such as wild pointers. Typical stack-based programming languages include C and C++.

Specifically for JavaScript, the implementation of closures depends on the related mechanism of its variable scope, and I also need to further study the memory model behind the JavaScript language.

pros and cons

In the face of closures, what we need to consider is whether to use them, and whether there are other alternatives without closures.

First of all, the biggest advantage of closures has been reflected in its application scenarios, avoiding the use of global variables, making variables "private" by functions, and long-term retention for multiple use.

However, closures also have their disadvantages, from the perspective of principle and implementation:

  1. The closure actively extends the life cycle of the related variables in the lexical space to which it is bound, and these variables will always be placed in memory until they are used up, occupying resources. leak problem.
  2. Secondly, the performance of the closure is not good, and some special scenarios need to pay attention to the writing method, such as the example in [2]:
 // bad case,每次构造器被调用时会对方法进行赋值
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };
     
  this.getMessage = function() {
    return this.message;
  };
}     
// good case,拆分出来
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

References

  1. https://en.wikipedia.org/wiki/Closure_ (computer_programming)
  2. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
  3. https://segmentfault.com/q/1010000040284976/a-1020000040286282
  4. https://www.liaoxuefeng.com/wiki/1022910821149312/1023021250770016
  5. https://zhuanlan.zhihu.com/p/307911919
  6. https://www.runoob.com/w3cnote/closure-intro.html
  7. https://books.studygolang.com/the-way-to-go_EN_CN/16.9.html
  8. https://www.calhoun.io/5-useful-ways-to-use-closures-in-go/
  9. https://riptutorial.com/java/example/14441/java-closures-with-lambda-expressions-
  10. https://stackoverflow.com/questions/220658/what-is-the-difference-between-a-closure-and-a-lambda

Hotlink
337 声望7 粉丝

Stay hungry, stay foolish.