Understanding Closures in javascript

  • 2021-07-10 18:21:01
  • OfStack

Reading catalog

What is a closure? Properties of closures The role of closures: Code example for closure Matters needing attention Summarize

Closure is an important concept in javascript, and it is also a technology used more in normal work. Come down and make a little summary of it

What is a closure?

Official statement:

A closure is a function that has access to variables in the scope of another function. A common way to create closures is to create a function inside a function and access the local variables of that function through the other function--"javascript Advanced Programming Version 3"

Here is a simple closure:


function A(){
 var text="hello world";
 function B(){
 console.log(text);
 }
 return B;
}
var c=A();
c(); // hello world   

Literally, the function B has access to the variable (text) in the scope of the function A, and the local variable text of this function is accessed through another function C. Therefore, the function B forms a closure. It can also be said that C is a closure, because C actually executes the function B.

It should be noted that A () is executed directly; There is no response. Because return B is not executed unless it is return B ();

Properties of closures

Closures have three properties:

1. Function nesting

2. External parameters and variables can be referenced inside a function

3. Parameters and variables are not collected by the garbage collection mechanism

Explain point 3 under 1, why are the parameters and variables of closures not collected by the garbage collection mechanism?

First, let's understand the garbage collection principle of javascript under 1:

(1) In javascript, if an object is no longer referenced, the object will be reclaimed by GC (garbage collection);

(2) If two objects refer to each other and are no longer referenced by the third, then the two mutually referenced objects will also be recycled.

In the above sample code, A is the parent function of B, and B is assigned a global variable C (the life cycle of the global variable will not end until the browser unloads the page), which causes B to always be in memory, while the existence of B depends on A, so A is always in memory and will not be collected by the garbage collection mechanism (garbage collection) after the call ends.

The role of closures:

In fact, the function of closure is also determined by the characteristics of closure. According to the above closure characteristics, the function of closure is as follows:

1. You can read the variables inside the function instead of defining a global variable to avoid polluting the environment

2. Keep the values of these variables in memory at all times.

Code example for closure

Here are some common closures and analyze them:

demo1 Accumulation of local variables.


function countFn(){
 var count=1;
 return function(){  // Function nested function 
 count++;
 console.log(count);
 }
}
var y = countFn(); // External functions are assigned to variables y;
y(); //2 //y Function call 1 The result is 2 , equivalent to countFn()()
y(); //3 //y Function call 2 The result is 3 , because on 1 The of the second call count It is also stored in memory and has not been destroyed, so accumulation is realized 
y=null; // Garbage collection , Release memory 
y(); // y is not a function

Since the variable count is still stored in memory after the first execution, it will not be recycled, so that the last value can be accumulated at the second execution. When y=null is introduced, the reference is destroyed and the memory is released

Using Closures in demo2 Loop

The code is as follows (the following three code examples): Our purpose is to call the loop sequence number in each loop:

demo2-1


for (var i = 0; i < 10; i++) {
 var a = function(){
 console.log(i)
 }
 a() // In order 0--9
}

The result of this example is no question, and we print 0-9 in turn

Each layer of anonymous function and variable i makes up a closure, but this is not a problem in the loop, because the function is executed immediately in the loop body

demo2-2

But not in setTimeout


for(var i = 0; i < 10; i++) {
 setTimeout(function() {
 console.log(i); //10 Times 10
 }, 1000);
}

What we expect is to print 0-10 in turn, but the actual situation is to print 10 times. Even if the time of setTimeout is changed to 0, 10 10s will be printed. Why is this?

This is because of a mechanism of setTimeout. setTimeout starts timing when the task queue ends. If there is a process before it ends, it waits until it ends before starting timing. Here, the task queue is its own loop.

setTimeout doesn't start timing until the end of the loop, so i in setTimeout is i in the last loop anyway. In this code, the last i is 10, so 10 10s are printed.

This is why the callback of setTimeout does not take the value of the loop every time, but takes the value of the last time

demo2-3

Solve the above problem that setTimeout can't print loops in turn


for(var i=0;i<10;i++){
 var a=function(e){
 return function(){
  console.log(e); // Enter in turn 0--9
 }
 }
 setTimeout(a(i),0);
}

Because the first parameter of setTimeout needs a function, a function is returned to it, and i is passed as a parameter at the same time. i is cached through formal parameter e, that is to say, e variable is equivalent to a copy of i and brought into the returned function.

When setTimeout is executed, it has a reference to e, and this value is not changed cyclically.

It can also be written in the following way, similar to the above:


for(var i = 0; i < 10; i++) {
 (function(e) {
 setTimeout(function() {
  console.log(e); // Print out in turn 0-9
 }, 0);
 })(i);
}

Adding Events to demo3 Loop

Look at a typical demo below.

We want alert to output the index value of li every time we click li, so use the following code:


<ul id="test">
 <li> No. 1 1 A </li>
 <li> No. 1 2 A </li>
 <li> No. 1 3 A </li>
 <li> No. 1 4 A </li>
</ul>
var nodes = document.getElementsByTagName("li");
for(i = 0,len=nodes.length;i<len;i++){
 nodes[i].onclick = function(){
 alert(i); // Values are all 4
 };
}

Contrary to expectations, no matter which li is clicked, it is alert (4), that is, it is the index value after the alert loop ends. Why is this?

This is because events are bound to different elements in the loop. If a variable related to the loop is called in the event callback function, this variable takes the last value of the loop.

Because the bound callback function is an anonymous function, in the above code, this anonymous function is a closure, which carries an outer scope (that is, the scope in for). When the event is triggered, the variables in the scope have already followed the loop to the end.

Another point is that the event needs to be triggered, and in most cases, the loop has ended when it is triggered, so the variable related to the loop is the last value.

To click li and alert to get the index value of li, the above code needs to be modified as follows:


<ul id="test">
 <li> No. 1 1 A </li>
 <li> No. 1 2 A </li>
 <li> No. 1 3 A </li>
 <li> No. 1 4 A </li>
</ul>
var nodes=document.getElementsByTagName("li");
for(var i=0;i<nodes.length;i++){
 (function(e){
 nodes[i].onclick=function(){
  alert(e);
 };
 })(i)
}

Solution: Add several corresponding closure domain spaces (anonymous functions are used here), which are specially used to store the contents (subscripts) that need to be referenced originally.

When the function is executed immediately, the e value will not be destroyed because it has an anonymous function inside it (it can also be said that the variable will not be destroyed because of the existence of closures). When executed, the e value is disconnected from the global variable i,

That is, at the time of execution, the e of the immediate execution function is as much as the i passed in, but the e value will not disappear because of the existence of anonymous functions.

The following solution can also be used, and the principle is like 1:


<ul id="test">
 <li> No. 1 1 A </li>
 <li> No. 1 2 A </li>
 <li> No. 1 3 A </li>
 <li> No. 1 4 A </li>
</ul>
var nodes=document.getElementsByTagName('li');
for(var i = 0; i<nodes.length;i++){
 (function(){
 var temp = i;
 nodes[i].onclick = function () {
  alert(temp);
 }
 })();
}

Matters needing attention

1. Causing memory leakage

Because closures carry the scope of the function that contains them, they take up more memory than other functions. Overuse of closures can lead to excessive memory footprint, so consider using closures only when absolutely necessary.

2. Using this in closures may also cause some problems.

Code example: from "js Advanced Programming 3";

Actually, our purpose is to want alert to produce name in object


var name="The Window";
 var object={
 name:"My Object",
 getNameFunc:function(){
  return function(){
  return this.name;
  }
 }
 }
 alert(object.getNameFunc()()); // The Window

Because in a global function, this equals window, and when a function is called as a method of an object, this equals that object. However, the execution environment of anonymous functions is global, so their this objects usually point to window.

When each function is called, it automatically takes two special variables: this and arguments. When the internal function searches for these two variables, it only searches for its active object. That is to say, return function inside will only search

Go to the global this and stop searching. Because it can never directly access these two variables in an external function.

With a slight modification, the this object in the outer scope is saved in a variable that can be accessed by a closure. This allows the closure to access the object.


function countFn(){
 var count=1;
 return function(){  // Function nested function 
 count++;
 console.log(count);
 }
}
var y = countFn(); // External functions are assigned to variables y;
y(); //2 //y Function call 1 The result is 2 , equivalent to countFn()()
y(); //3 //y Function call 2 The result is 3 , because on 1 The of the second call count It is also stored in memory and has not been destroyed, so accumulation is realized 
y=null; // Garbage collection , Release memory 
y(); // y is not a function
0

We assign the this object to the that variable. After the closure is defined, the closure can also access this variable. Therefore, that still references object even after the function returns, so calling object. getNameFunc () () returns "My Object".

Summarize

When other functions are defined inside the function, closures are created. Closures have access to all variables inside the containing function.

The scope of a closure contains its own scope, the scope of the function, and the global scope.

When a function returns 1 closure, the scope of the function is saved in memory until the closure does not exist.

Extra scope must be maintained when using closures, so overuse of them may consume a lot of memory


Related articles: