Detailed explanation of js closure

Closure

Classic real questions

  • What is a closure? What are the application scenarios of closures? How to destroy closure?

what is closure

Closure is a very important knowledge point in JavaScript , and it is also one of the knowledge points that are more likely to be asked in our front-end interviews.

Open " JavaScript Advanced Programming" and " JavaScript Definitive Guide", and you will find that there are different explanations of closures. Searching for closures on the Internet, you will also find that there are different opinions, which makes this knowledge point itself appear to be different. A bit mysterious, even a bit fantasy.

So is this knowledge point really that profound?

No! In fact, it is very easy to understand closures in JavaScript , but before that, you need to know the following two knowledge points:

  • Scope and scope chain in JavaScript
  • Garbage collection in JavaScript

Here we briefly review these two knowledge points:

1. Scope and scope chain in JavaScript

  • A scope is an independent domain, so that variables will not be leaked or exposed, and variables with the same name in different scopes will not conflict.
  • The scope is fixed when it is defined and does not change.
  • If the value is not found in the current scope, it will be searched in the upper scope until the global scope is found. The chain formed by such a search process is called a scope chain.

2. Garbage collection in JavaScript

  • The Javascript execution environment will be responsible for managing the memory used during code execution, which involves a garbage collection mechanism
  • The garbage collector will periodically (periodically) find those variables that are no longer used. As long as the variable is no longer used, it will be recycled by the garbage collector and then release its memory. If the variable is still in use, it will not be recycled.

OK , with these two knowledge points in mind, let's look at what closure is.

Closure is not a specific technology, but a phenomenon, which means that when defining a function, information in the surrounding environment can be used in the function. In other words, when a function is executed, whenever data outside the function is used, a closure is created.

The scope chain is the means to realize the closure.

What? As long as external data is used in a function, a closure is created?

Really? Below we can prove it:

image-20211227145016552

In the above code, we define a variable i in the function a , and then print this i variable. For the function a , the variable i exists in its own function scope , so we can see the variable i exists in Local when debugging .

Let's modify the above code slightly, as shown below:

image-20211227145521272

In the above code, we place the action of declaring the i variable outside the a function. This means that the a function can no longer find the i variable in its own scope . What will it do?

If you have learned the scope chain, you must know that it will look out layer by layer along the scope chain. However, as mentioned above when introducing closures, if this happens, that is, when the function uses external data, a closure will be created.

Carefully observe the debugging area, we will find that i is placed in the Closure at this time , thus confirming our previous statement.

So you see, closures are actually not that difficult to understand. When you feel that a word is particularly difficult for you, you can also use the word splitting method. This is also a tried and tested method that I recommend.

"Closed" can be understood as "closed, closed loop", and "package" can be understood as "a space similar to a package". Therefore, closure can actually be regarded as a closed space. So what is this space used for? In fact, it is used to store variables.

image-20211227163947135

So will all variable declarations under a function be put into the closed space of the closure?

Not really, whether to put it in the closure depends on whether there is a reference to this variable elsewhere, for example:

image-20211227164333723

In the above code, no variables are created in function c, but i, j, k , and x are printed . These variables exist in functions a, b, and the global scope respectively. Therefore, three closures are created, and the global The value of i is stored in the closure , the values ​​of variables j and k are stored in closure a , and the value of variable x is stored in closure b .

But if you look carefully, you will find that the y variable in function b is not placed in the closure, so whether to put it in the closure depends on whether the variable is referenced.

Of course, you may have such a new problem at this time, so many closures, wouldn't that take up memory space?

In fact, if it is an automatically formed closure, it will be destroyed. For example:

image-20211227174043786

In the above code, we try to print out the variable k on line 16. Obviously, an error will be reported at this time. By setting a breakpoint on line 16 for debugging, we can clearly see that there is no closure at this time. The garbage collector will automatically recycle unreferenced variables without any memory usage.

Of course, what I am referring to here is the automatic generation of closures. Regarding closures, sometimes we need to manually create a closure based on requirements.

Consider the following example:

function eat(){
    
    
    var food = "鸡翅";
    console.log(food);
}
eat(); // 鸡翅
console.log(food); // 报错

In the above example, we declared a function called eat and called it.

The JavaScript engine will create an execution context for the eat function, in which the food variable is declared and assigned a value.

When this method is executed, the context is destroyed and the food variable disappears. This is because the food variable is a local variable of the eat function. It acts in the eat function and will be created and destroyed as the execution context of eat is created. So when we print the food variable again, an error will be reported, telling us that the variable does not exist.

But let's modify this code slightly:

function eat(){
    
    
    var food = '鸡翅';
    return function(){
    
    
        console.log(food);
    }
}
var look = eat();
look(); // 鸡翅
look(); // 鸡翅

In this example, the eat function returns a function, and the local variable food is accessed in this inner function . Call the eat function and assign the result to the look variable. This look points to the internal function in the eat function, then calls it, and finally outputs the value of food .

The reason why food can be accessed is very simple. As we said above, the garbage collector will only recycle variables that are not referenced, but once a variable is still referenced, the garbage collector will not recycle this variable. In the above example, food should be destroyed after calling eat , but we returned the anonymous function inside eat to the outside , and this anonymous function references food , so the garbage collector will not recycle it. Yes, this is why the value of the food variable can still be printed out when this anonymous function is called outside .

At this point, one of the advantages or characteristics of closures has been revealed, that is:

  • Closures allow the external environment to access local variables inside a function.
  • Closures allow local variables to be persisted and not destroyed along with their context.

With this feature, we can solve a global variable pollution problem. In the early days when JavaScript could not be modularized, when multiple people collaborated, defining too many global variables might cause global variable naming conflicts. Closures were used to solve function calls to variables and write variables to an independent space. Inside, which can solve the problem of global variable pollution to a certain extent.

For example:

var name = "GlobalName";
// 全局变量
var init = (function () {
    
    
    var name = "initName";
    function callName() {
    
    
        console.log(name);
        // 打印 name
    }
    return function () {
    
    
        callName();
        // 形成接口
    }
}());
init(); // initName
var initSuper = (function () {
    
    
    var name = "initSuperName";
    function callName() {
    
    
        console.log(name);
        // 打印 name
    }
    return function () {
    
    
        callName();
        // 形成接口
    }
}());
initSuper(); // initSuperName

Well, at the end of this section, let's make a small summary of closures:

  • A closure is a closed space that stores values ​​of the scope that are referenced elsewhere. In JavaScript , closures are implemented through scope chains.

  • As long as external data is used in the function, a closure is created. In this case, we do not need to care about the closure created when coding.

  • We can also manually create closures through some means, so that the external environment can access the local variables inside the function, so that the local variables can be continuously saved and not destroyed along with its context.

Closure classic problem

After talking about closures, let’s look at a classic problem of closures.

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

In the above code, our expected result is to output the values ​​of the i variable as 1, 2, and 3 after 1 second . However, the execution result is: 4, 4, 4 .

In fact, the problem lies with closures. You see, the setTimeout in the loop accesses its outer variable i , forming a closure.

There is only one i variable , so the same variable is accessed in setTimeout that loops three times . When the loop reaches the 4th time , the i variable increases to 4 , the loop condition is not met, the loop ends, and the context ends after the code is executed. However, the three setTimeouts wait for 1 second before executing. Due to the closure, they can still access the variable i , but the value of the i variable is already 4 at this time .

To solve this problem, we can let the anonymous function in setTimeout no longer access external variables, but access its own internal variables, as follows:

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

In this way, there is no need to access the variable i declared in the for loop in setTimeout . Instead, I pass the value of variable i to setTimeout by calling a function to pass parameters , so that they no longer create a closure, because the variable i can be found in my own scope .

Of course, there is an easier way to solve this problem, which is to use the let keyword in ES6 .

The variable it declares has block scope. If you put it in a loop, then there will be a new variable i every time it loops , so even if there is a closure, it will be no problem, because each closure saves a different i variable, then the problem just now will be solved.

for (let i = 1; i <= 3; i++) {
    
    
    setTimeout(function () {
    
    
        console.log(i);
    }, 1000);
}

Answers to real questions

  • What is a closure? What are the application scenarios of closures? How to destroy closure?

A closure is a closed space that stores values ​​of the scope that are referenced elsewhere. In JavaScript , closures are implemented through scope chains.

As long as external data is used in the function, a closure is created. In this case, we do not need to care about the closure created when coding.

We can also manually create closures through some means, so that the external environment can access the local variables inside the function, so that the local variables can be continuously saved and not destroyed along with its context.

Using closures can solve a global variable pollution problem.

If it is an automatically generated closure, we do not need to worry about the destruction of the closure. If it is a manually created closure, we can set the referenced variable to null, that is, manually clear the variable, so that the next time the JavaScript garbage collector performs garbage collection When recycling, if it is found that this variable has no references, it will recycle the amount set to null .


-EOF-

Guess you like

Origin blog.csdn.net/qq_53461589/article/details/132740029