Before writing this article, I was vague about the concept and principle of closures, and had always been wrapping the inner layer with the popular outer function…. To deceive yourself. I’m not saying it’s right or wrong, I just don’t want to have a herd mentality or it could be said that if we say something better and lower, the pussy would go up several notches…

Speaking of closures, it is one of the two core technologies of JavaScript (asynchronous and closure), in the interview and practical application, we can not do without them, even can say that they are an important indicator to measure the strength of JS engineers. Here we list a few common questions about closures to help you understand and define what closures are in your mind in terms of answering them.

The questions are as follows:



1.What is a closure?2.Can you explain how closures work?3.How do you use closures?Copy the code

Introduction to closures

Let’s take a look at some of the book introductions:

1. Closures are functions that have access to variables in the scope of another function

2. Function objects can be associated by scope, and variables in the function body can be stored in the function scope, which is called “closure” in computer science literature. All javascirpt functions are closures

Closures are a corollary of writing code based on lexical scope.

4.. Functions can be related to each other through scope chains, and variables inside functions can be stored in other function scopes, a property known in the computer science literature as closures.

So, they all have their own definitions, but they all mean the same thing. Before this, the author was aware of it but did not know why, and finally spent a day from lexical scope to the concept of scope chain and then to the formation of closure to do a general comb, found that people are very clear… .

Let’s get rid of these abstract and obscure statements, start from the beginning, internalize and finally summarize a sentence of our own about closures. I think this is very helpful for interviews and to enrich the theoretical knowledge of the developers themselves.

Closure composition

Lexical scope

To understand the lexical scope, we have to speak of the compilation phase of JS, JS is weakly typed language is known to all, the so-called weak type refers to the storage type, without the predefined variables is not entirely summarized JS or with other language difference, here we quote beige book (you don’t know javascript) compiled languages on the explanations.

Compiled languages

A compiled language must pass through three phases before it can be executed. These three phases act as filters to convert the code we write into specific executable code within the language. So let’s say we wrote the code var a = 1; The JS engine defines the format var,a,=,1, which needs to be converted at compile time. This is just a metaphor, but in fact this is just what you do in the first phase of the compile phase. So let’s summarize what we did in each of the three phases.

  1. Tokenizing/Lexing This is exactly what we’ve been talking about. In fact, we’re writing code that’s just strings, and in the first stage of compilation, we’re converting those strings into toekNs, which we can think of as expressions that we’ve decomposed above. (Note that there are two possibilities for this step, currently this is word segmentation, while lexical analysis is said below along with lexical scope.)

  2. With the lexical unit in place, JS had to continue Parsing the syntax in the code to lighten the load on the JS engine. Abstract Syntax Tree is generated through lexical unit. Its function is to construct a program Syntax Tree for JS engine, which is called AST for short. We can’t help but think of Dom trees (a little bit further). Yes, they are all trees. Var,a,=,1, for example, will divide them into layers, for example: The top layer has a stepA containing the “V “, stepB below the stepA, stepB contains the” A “, so a layer by layer nested down….

  3. Var a = 1; var a = 1; var a = 1; var a = 1; For example, the compiler first asks the scope if it currently has variable A, and ignores it if it does, otherwise it creates a variable named A under the current scope.

Lexical phase

Ha ha, finally arrived at the lexical stage, is not to see the above three stages, very meng force, DID not expect JS will have such a tedious experience? In fact, the above summary is just the most basic process of all compiled languages, for our JS, it does not only those things in the compilation stage, it will do some performance optimization for the JS engine in advance, in short, the compiler does all the dirty work.

To talk about the lexical stage, we also combine the word segmentation/lexical analysis stage that has not been concluded above. For…

Lexical scoping occurs during the first step of the compilation phase, the word segmentation/lexical analysis phase. It has two possibilities, word segmentation, which is stateless, and lexical analysis, which is stateful.

So how do we determine whether there is any state? For example, var a = 1, if the lexical unit generator calls a stateful parsing rule when determining whether a is a separate lexical unit (the generator doesn’t know if it depends on other lexical units, so it has to parse further). On the other hand, if it is not defined by the generator and is not defined by semantics, it is included in the participle.

Now we know that if the lexical unit generator is not sure whether the current lexical unit is independent, it enters the lexical analysis, otherwise it enters the word segmentation stage.

Yes, this is the basis for understanding lexical scopes and where they come from.

Simply put, lexical scope is the scope of a definition at the lexical stage. Lexical scope is where you write the variable and block-level scope when you write the code. When a lexical parser (which is treated only as a lexical parser, as described below) processes code, the scope remains the same (except for dynamic scope).

In this section, we only need to know:

  1. What is the lexical scope?

  2. What is the concept of word segmentation/lexical analysis in lexical stage?

  3. How do they influence the formation of lexical scope?

There are two omitted topics in this section (lexical parser, dynamic scope) that are not covered due to subject limitations, but will be covered in the future. Let’s start scoping.

The scope chain

1. Execution environment

The execution environment defines other data that a variable or function has access to.

The environment stack can be thought of temporarily as an array (a storage stack for the JS engine).

In web browsers, the global environment (window) is the outermost execution environment, and each function has its own execution environment. When a function is called, the function will be pushed into an environment stack. When it and its dependent members are all executed, the stack will pop up its environment.

First look at a picture!

An environment stack is also called a function call stack (the same thing, but the latter is named more in favor of functions), and we refer to it collectively as a stack. The outermost layer in the environment stack is the Window, which is destroyed from the stack only when the browser is closed. And each function has its own execution environment,

By this point we should know:

  1. Each function has a corresponding execution environment.

  2. When the function is executed, the environment of the current function will be pushed into the environment stack. When the current function is executed, the environment will be destroyed.

  3. The window global object is the outer layer of the stack (the bottom, as opposed to the image).

  4. The difference between a function call stack and an environment stack. These two are like primitive and primitive types in JS | reference types and object types and compound types!

2. Variable objects and active objects

Implementation environment, the so-called environment we are not difficult to associate with the concept of the house. Yes, it is like a big house, it is not independent, it will carry or associate with other concepts in order to accomplish more tasks.

Each execution environment has a variable object ——-, which stores all the variables and functions in the current environment.

The variable object is important to the execution environment because it is created before the function is executed. It contains all the parameters, variables, and functions of the current function. The process of creating a variable object is actually the process of initializing the data inside the function (function parameters, internal variables, internal functions).

Properties in variable objects cannot be accessed until the current environment is executed! However, after the execution phase, the variable object is transformed into an active object, and its properties can be accessed, and then the execution phase operation begins. So the live object is really just another form of the variable object when it is actually executed.



 function fun (a){
    var n = 12;
    function toStr(a){
        return String(a); }}Copy the code

In the context of fun, there are three variable objects (before being pushed into the context stack). The first is arguments, the variable n and the function toStr, after being pushed into the context stack (during execution), are fun’s active objects. Live objects start out with just one variable, argumens objects.

By this point we should know:

  1. Each execution environment has a variable object corresponding to it.

  2. All variables and functions defined in the environment are stored in this object.

  3. For functions, the initialization phase before execution is called a variable object, which becomes an active object during execution.

3. Scope chain

When code is executed in an environment, a scope chain of variable objects is created. The structure of the scope chain expressed in data format is as follows.

[{current environment variable object}, {outer variable object}, {outer variable object}, {window global variable object}] Each array unit is a piece of the scope chain, and this block is our variable object.

The front end of the chain is always the variable object of the environment in which the code is currently executing. The variable object of the global execution environment is always the last object in the chain.



    function foo(){
        var a = 12;
        fun(a);
        function fun(a){
             var b = 8;
              console.log(a + b);
        }
    }  
    
   foo();Copy the code

In this simple example above, we can start by thinking, what are the variable objects in each execution environment? What are the variable objects of these two functions?

Use Fun as an example. When we call fun, we create an active object with arguments, A, b. For functions, the active object contains only one variable, arguments, at the beginning of execution (create other active objects when the execution stream enters).

In an active object, it still represents the current set of parameters. For function active objects, we can think of two parts, the fixed arguments object and the local variables in the function. In this case, both A and B are counted as local variables, even though A is already included in arguments.

Have you noticed that in the environment stack, all execution environments can form the corresponding scope chain? We can concatenate a chain of relative scopes in the environment stack very intuitively.

Here’s an overview of how this code works:

  1. When foo is created, the Scope chain already contains a global object, stored in the internal property [[Scope]].

  2. After executing foo and creating the execution environment and the live object, we extract the function’s internal properties [[Scope]] to build the Scope chain of the current environment (after fetching, we only have the global variable object, and then append an live object of its own).

  3. Fun is encountered during execution and continues with the previous step on Fun.

  4. Fun completes execution and is removed from the environment stack. Foo is therefore done and continues to move out.

  5. Javscript listens when foo is not referenced by any variables and implements a garbage collection mechanism to clear the occupied memory.

A scope chain is simply a list of Pointers to variable objects that reference the current execution environment; it refers to, but does not contain. Because its shape is like a chain and its execution is very similar, we call it a chain of scope, and when we understand this mystery, we can throw off the formal shackles and start from the principle.

By this point we should know:

  1. What is a scope chain?

  2. Scope chain formation process.

  3. The concept of internal attributes [[Scope]].

Using closures

From beginning to end, we went over all the technical points involved, which were not very detailed and somewhat inaccurate, because we only had a rough idea of the process without any proof of fact.

The theory involved is fleshed out, so now we’re going to use it. Let’s start with a few simple counter examples:



 var counter = (!function(a){
    var num = 0;
    return function(a){ return  ++num; }
 }())

 function counter(a){
        var num = 0;
        return {
            reset:function(a){
                num = 0;
            },
            count:function(a){
                returnnum++; }}}function counter_get (n){
    return {
        get counte(){
        return ++n;
        },
        set counte(m){
            if(m<n){ throw Error("error: param less than value"); }
            else {
                n = m; returnn; }}}}Copy the code

I believe that many of you have predicted the results of their implementation. They all have the small feature that the implementation returns a function object with a reference to an external variable.

Why return a function? Because functions can provide an execution environment in which variable objects from other environments are referenced, the latter will not be cleared by the JS internal recycling mechanism. So when you access it in the current execution environment, it’s still in memory. It is important not to confuse the two important processes of the environment stack with the garbage collection. The environment stack is commonly referred to as the call stack, which moves in and moves out, and the garbage collection is listening for references.

Why is it increasing all the time? As mentioned above, the returned anonymous function forms an execution environment in which the variable object under the scope chain is not its own, but in another environment. Because it references someone else, JS doesn’t garbage collect it. So this value is always there, incrementing it with each execution.

Is there a performance loss? For this feature, we use closures to implement it, but what happens when we’re done? Don’t forget that there is also a reference to another variable object. In order for js to recycle it normally, we can manually assign it to null;

Take the first one:



  var counter = (!function(a){
    var num = 0;
    return function(a){ return  ++num; }
 }())
 var n = counter();
 n(); n();
 
 n = null;  // Empty references and wait for recycling
 Copy the code

The first one returns a function, and the second two are similar to methods. They are both very straightforward to indicate the implementation of a closure, but the diversity of the implementation is worth noting.

Closure interview questions

Implement a closure timer with the property’s accessor

See above cases;

Look at the code and guess the output



function fun(n,o) {
  console.log(o);
  return {
    fun:function(m){
      return fun(m,n); }}; }Copy the code

var a = fun(0); a.fun(1); a.fun(2); a.fun(3); //undefined,? ,? ,? var b = fun(0).fun(1).fun(2).fun(3); //undefined,? ,? ,? var c = fun(0).fun(1); c.fun(2); c.fun(3); //undefined,? ,? ,?

The difficulty of this problem is not only closure, but also recursion and other processes. When I answered this question, I also got it wrong, which was really disgusting. So let’s analyze it.

First, the closure part. Fun returns an available one. The fun method accessed by the operator. Its active objects in the returned method can be divided into [arguments[m],m,n,fun]. In the problem, these live objects are used with variable references (which receive the returned function).

In the function returned, there is an external argument, m, which is called again and returns fun. This time fun is executed with two arguments. The first is the external argument (that was assigned to it at the time of the call), and the second is the first argument to fun.

First, the return of fun is assigned to the variable a, then call returns of fun alone, the second parameter n in the fun function returns to our last time by calling the outer parameters of fun back again, but it is not a chain, we call the four times, but it four times, only the first call to external transfer in fun, The internal fun called by a later does not affect the output of o, so it is not hard to see that the final result is undefine 0,0,0.

The second is the chain call, at first glance, and the first no difference ah, but the first is more than a variable in the middle of the A, must not be confused by the present ah!!



    // First call method a.fin (1) a.fin (2) a.fin (3)
    {
        fun:function(a){
              return fun(a)  // Outer fun}}Fun (1).fun(2).fun(3)
    // The first call returns exactly the same as above
    // It was different after the second time
    return fun(a)  // Return directly to external fun
    Copy the code

The second difference is that on the second call it again receives the return of {fun:return fun}, whereas on the third call it is the external fun function. By understanding the first and the second I believe I know the third. The final result will not say, you can test yourself.

Look at the code and guess the output



   for (var i = 1; i <= 5; i++) {
  setTimeout( function timer() {
      console.log(i);  
  }, 1000 );
  }

 for (var i = 1; i <= 5; i++) {
    (function(i){
        setTimeout( function () {
              console.log(i);
          },  1000 );}) (i);
 }Copy the code

This is an asynchronous problem. It is not a closure, but we can solve it with a closure.

The second code will print 1-5 because each callback refers to the parameter I (that is, the live object), whereas in the previous loop each callback referred to the variable I. There are other, simpler ways to do this.



       for (let i = 1; i <= 5; i++) {
               setTimeout( function timer() {
                          console.log(i);  
              }, 1000 );
  }Copy the code

Let creates the local scope for us, which is the same as the closure solution we just used, except that js creates temporary variables internally and we don’t have to worry about it having too many references and running out of memory.

conclusion

We got it

This chapter covers a bit more ground, mainly to give you a fuller picture of closures, so what do you know so far? I think everyone has the answer in mind.

1. What are closures?

Closures are a corollary of lexical scope. The result is that the active object of a function cannot be reclaimed by implicitly referencing it, yet its scope chain can still be accessed by reference.



    (function(w,d){
            var s = "javascript";} (window,document))Copy the code

Some people call this method closure, and say that closure can avoid global pollution. First of all, you should have an answer here. Is the above example a closure?

Avoiding global contamination is true, but closures are not. At most, they create a new secondary scope on top of the global execution environment, avoiding defining other variables globally. Remember that it’s not really a closure.

2. Can you explain the principle of closures?

In conjunction with what we have said above, its roots begin in the lexical stage, where lexical scope is formed. Finally, a scope chain composed of variable objects is formed according to the environment stack generated by the calling environment. When an environment is not garbage collected by JS normally, we can still access its original scope chain by reference.

3. How do you use closures?

There are many scenarios for using closures. Recently, I have been looking at functional programming. It can be said that closures are an important foundation of functions in JS, for example, an incomplete function.



  function calculate(a,b){
    return a + b;
 }

 function fun(){
    var ars = Array.from(arguments);
  
    
    return function(){
        var arguNum = ars.concat(Array.from(arguments))
        
        return arguNum.reduce(calculate)
    }
}

var n = fun(1.2.3.4.5.6.7);

var k = n(8.9.10);

delete n;
Copy the code

Of course, there are more complex cases in our daily development, which require many function blocks, and at that point we will see the true power of closures.

The article here is probably finished, are my own thin view and some of the content of the book, I hope to have a little impact on you, of course, this is positive… If there is a description in the article is not appropriate or we have a better view also hope to point out, thank you.

An aside:

It only takes a few minutes to read an article or a few pages of a book. But understanding requires a process of personal internalization, from input to understanding to internalization to output, which is a very reasonable system of knowledge. I don’t think it’s just closures, it’s just as important for any knowledge, and when some knowledge comes into our bodies, it needs to be exported and told to others. It’s not just about dedication, it’s about self-improvement.