preface

If you want to learn more about JavaScript, the concept of closures is almost impossible to escape. Today let’s take a look at what closures are. If you have no basic knowledge, you can read the previous article to help you understand the content of this article better:

  • Interesting scope and enhancement

Take a look at closures

1. What are closures

Let’s first look at the definition of closures in MDN:

A combination of a function bound with references to its surrounding state (lexical environment) (or the function enclosed by references) is a closure.

The definition of a closure is very obscure, and if you can explain it in a layman’s way, it is:

  • A combination of a function defined inside a function and its scope is called a closure.

This may sound very abstract, but we have a concept that will be explained in more detail. There is another explanation for closures:

  • A function that declares an inner function and returns an inner value that can be accessed by the outside world is called a closure

Unlike the first interpretation, the second one calls the function itself a closure, which is reasonable, but it’s more accurate to call the function a closure. In this case, there is no problem with either explanation, and it can exist at the same time.

2. Feel the closure

Now that we’ve looked at the concept of closures, let’s go through a real example and get a feel for what a closure looks like. What are the conditions under which closures arise?

What does a closure look like?

function outer(){
	var a=0;
    function inner(){
		console.log(++a);
	}
    return inner;
}
var f=outer();
f();  / / 1
f();  / / 2
Copy the code

Let’s take a look at how closures are represented in this example. We first define outer, then declare an inner function inside outer and return it.

On the outside, we call outer and store the returned inner function in f, which is now what we call a closure.

In our example, we execute f twice, printing 1 and 2 respectively, indicating that we are executing console.log(++a) in f; Statement, according to our previous interpretation of the scope, we will look for the variable in the chain of scopes in which the function is located. Finally, we find the variable in outer and increment it.

It is also important to note that our output is not a double 1, indicating that our scope is shared, so we can see outer’s scope extending outward.

The outer scope is supposed to be released at the end of execution, but according to the GC mechanism (we won’t expand it here) :

  • I’m ready to releaseouterNow, butaThe variable seems to be referenced, and it’s stored externally and it doesn’t know when to call it? So bam, I’m not going to release it.

The closure is thus born, and variables in the scope can still be accessed by outsiders after the scope has disappeared.

What are the conditions under which closures arise?

Let’s summarize what it takes to generate a closure based on the examples and explanations we’ve just given:

  1. Declare an inner function in an outer function and return the inner function.

    Here,outerIt’s the external function,innerThat’s the inner function that’s returned.
  2. The inner function refers to a variable in the outer function.

    In our case,innerThe function refers to the variablea.
  3. The external function needs to be executed, creating a closure, and the returned inner function needs to be saved.

    The outer functionouterIt needs to be executed, and it needs to save the returned internal function, as in our examplevar f=outer().

Closures can only be generated if all three conditions are met. While this may seem difficult to achieve, in fact, closures are often generated inadvertently in our actual code.

Now that you have an idea of closures, let’s take a closer look at all aspects of closures.

Take a closer look at closures

1. Create multiple closures

In the example above, we created a closure and executed it multiple times to increment variable A in the same scope. Let’s see what happens when we create multiple closures:

function outer() {
  var a = 0;
  function inner() {
    console.log(++a);
  }
  return inner;
}
var f1 = outer();
var f2 = outer();
f1();  / / 1
f2();  / / 1
Copy the code

This code is modified from the previous example, where we execute the external function twiceouterAnd use different variablesf1andf2Save. When you performf1andf2You’ll find that the output is zero1That * *f1andf2Is scoped independently,f1andf2There are two different closures **. Let’s use a diagram to understand:

When f1 and F2 were created, respectively, the outer function was called twice, creating two different contexts. When f1 and F2 are executed separately, variables will be searched in the corresponding scope according to the search rules of the scope, and value-added output will be performed, so the final two values are 1.

2. Modularity with closures

We know that the outside of the scope can’t get the inside value of the scope. We can use closures to expose values or methods that the scope needs, for example:

function outer() {
  var myNumber = 0;
  function addNumber() {
    myNumber++;
  }

  function getNumber() {
    return myNumber;
  }

  return {
    addNumber, getNumber
  }
}

var module = outer();
module.addNumber();
module.addNumber();
module.getNumber();
Copy the code

In this example, we also define an outer function, outer, and two functions, addNumber and getNumber, respectively, to increment and retrieve the variable myNumber.

When we execute outer(), creating a context and storing the internally returned object in the Module variable, we create a closure and an object containing the methods addNumber and getNumber.

Since the variable myNumber is not directly accessible to the outside world, but is accessible to addNumber and getNumber due to closures, we have successfully hidden the variable myNumber and provided only methods to increase and obtain the value of myNumber.

Try to solve the problem with closures

We’ll try to use closures to solve practical problems. Let’s take a look at some examples:

for (var i = 0; i < 2; i++) {
      setTimeout(() = > {
        console.log(i);
      }, 500);
    }
Copy the code

This is a very misleading example. If you’ve ever been around, you know that instead of printing a 0 and a 1, you end up printing two 2’s, so let’s see why that happens. First, outside is a for loop that executes twice.

for (var i = 0; i < 2; i++) {
	...	// Execute twice
}
Copy the code

Inside the function, we call the setTimeout function. The key is that this function is asynchronous and does not execute immediately, so it does not actually execute the setTimeout function until the external for loop has finished executing. The second key point is that in a for loop, the variables defined by var are defined globally, and there is no block-level scope. So this code right here can be viewed almost like this.

var i=0;
i++;	//i=1
i++;	//i=2
console.log(i);
console.log(i);
Copy the code

It’s pretty straightforward, but it’s pretty clear why the output is a double 2, because we all share the same scope, and the value of I is overwritten. So knowing what the problem is, let’s try using the closures we just learned to create different scopes:

   for (var i = 0; i < 2; i++) {
      function outer() {
        var j = i;
        setTimeout(function inner() {
          console.log(j);
        }, 500);
      }
      outer();
    }
Copy the code

We’ve modified this code to look like a closure. The setTimeout is not directly an inner function, because it defines the inner function and saves the ability to execute it.

As you can see, the final output is 0 and 1, indicating that our closure is successful. However, this closure is rather bulky, so we will try to improve it and write it more gracefully:

   for (var i = 0; i < 2; i++) {
        (function() {	// Replace the outer function definition with a self-executing function and execute both steps
            var j = i;
            setTimeout(function inner() {
                console.log(j);
            }, 500); }) (); }Copy the code

We can simplify it even further:

for (var i = 0; i < 5; i++) {
        for (var i = 0; i < 2; i++) {
            (function(j) {// Replace the outer function definition with a self-executing function and execute both steps
                setTimeout(function inner() {
                    console.log(j);
                }, 500);
            })(i);//var j= I step, replaced by passing in the I value}}Copy the code

And you’re done!

conclusion

This paper first introduces the definition of closure, and different people’s understanding of closure, then introduces the cause of the closure and sums up the three conditions, and then illustrates how to create multiple closure by closure module, finally tells the story of how to solve the for loop through closure used in asynchronous functions, in turn, the output value. Closures are not as scary as you might think, but if you are willing to take the time to explore and understand them, they are also open to you

Write in the last

All see here, if feel helpful to you might as well click a like to support bai ~

More articles and knowledge points will be updated in the future. If you are interested, you can pay attention to a wave of ~

If there are any mistakes or inaccurate descriptions, we welcome you to point out and communicate ~