JavaScript advanced tutorial - an article to let you understand scope chain and closure

Posted by REDFOXES06 on Fri, 21 Jan 2022 00:01:27 +0100

catalogue

1. Scope

2. Scope chain

3. Pre analysis

3.1 variable pre analysis

3.2 function pre analysis

4. Closure

4.1 closure cases:

4.2 closure praise cases

5. The role of closures

6. Some problems caused by closures

6.1 first: use more closures

6.2 the second method uses anonymous closures

6.3 the third method: use the let keyword introduced in ES2015

6.4 the fourth method: use {forEach() to traverse

7. Performance

8 summary

1. Scope

In JS, variables can be divided into local variables and global variables. For those unfamiliar with variables, please see my article:

https://blog.csdn.net/bznhlhh/article/details/118600660

Scope is the use scope of variables, which is divided into local scope and global scope. The use scope of local variables is local scope, and the use scope of global variables is global scope. Before ECMAScript 2015 introduced the let keyword, there was no block level scope in JS -- that is, variables defined in a pair of curly braces ({}) in JS can still be used outside the curly braces.

2. Scope chain

When the internal function accesses the variable of the external function, it is obtained by chain search. Search layer by layer from the inside to the outside, and use it directly when it is found. If the variable is still not found when it is searched to the level 0 scope, an error will be reported. This structure is called scope chain.

// Scope chain: the use of variables, from the inside out, layer by layer search, search directly
// If this variable is still not found when searching for level 0 scope, an error will be reported
var num = 10; //Scope chain level: 0

function f1() {
	var num2 = 20;

	function f2() {
		var num3 = 30;
		console.log(num); // >10
	}
	f2();
}
f1();

3. Pre analysis

JS code is parsed and executed by the JS engine in the browser, which is divided into two steps: pre parsing and code execution. Pre parsing is divided into variable pre parsing (variable promotion) and function pre parsing (function promotion). Before the browser JS code runs, the declaration of variables and functions will be advanced (promoted) to the top of the scope.

3.1 variable pre analysis

Promote the declaration of all variables to the front of the current scope without promoting the assignment operation.

Example:

console.log(num); // No error is reported. An undefined is returned
var num = 666;

After pre parsing:

// After pre parsing: variable promotion
var num;
console.log(num); // So an undefined is returned
num = 666;

3.2 function pre analysis

Promote all function declarations to the front of the current scope.
Example:

f1(); // It can be called normally

function f1() {
	console.log("Albert The singing is so beautiful");
}

After pre parsing:

function f1() {
	console.log("Albert The singing is so beautiful");
}
f1(); //After pre parsing, the code is executed line by line. After executing to f1(), call the function f1()

4. Closure

The explanation of closures in professional books is that closures in JavaScript refer to the combination (closed) of a function bound with the reference of the surrounding state (lexical environment). In JavaScript, closures are created at the same time every time a function is created. Closure is a mechanism to protect private variables. When a function is executed, it forms a private scope to protect the private variables from external interference, that is, it forms a stack environment that is not destroyed.
This sentence is difficult to understand. My understanding of closures is that in function A, there is A function B. in function B, the variable or data X defined in function A can be accessed, and the accessed variable x can exist together with function B. Even if function A has finished running and the environment for creating variable x is destroyed, the X variable in function B will still exist until the B function accessing variable x is destroyed. At this time, A closure is formed. As shown in the following code:

function A() {
	var x = 0;

	function B() {
		return ++x;
	}
	return B // Return B function
}

var B = A(); // Create B function
console.log(B()); // 1
console.log(B()); // 2
console.log(B()); // 3
console.log(B()); // 4
console.log("%c%s", "color:red", "*******---------*********");
// Create a new B function
B = A();
console.log(B()); // 1

4.1 closure cases:

// Ordinary function
function f1() {
	var num = 0;
	num++;
	return num;
}
console.log(f1());
console.log(f1());
console.log(f1());

console.log("%c%s", "color:red", "*******---------*********");
// closure
function f2() {
	var num = 0;
	return function() {
		num++;
		return num;
	}
}
var ff = f2();
console.log(ff()); // 1
console.log(ff()); // 2
console.log(ff()); // 3

4.2 closure praise cases

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Scope chain and closure</title>
    <style>
        *{
            padding: 0;
            margin: 0;
        }
        li {
            list-style: none;
            margin-left: 30px;
            margin-top: 20px;
        }
        img {
            /* width: 500px; */
            height: 300px;
            object-fit: cover;
        }
        div {
            display: flex;
            justify-content: flex-start;
            flex-wrap: wrap;
        }
        input {
            display: flex;
            justify-content: center;
            align-items: center;
            margin: auto;
            width: 50px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div>
        <li>
            <img src="/images/1.jpg" alt="" title="">
            <input type="button" value="(1)fabulous">
        </li>
        <li>
            <img src="/images/2.jpg" alt="" title="">
            <input type="button" value="(1)fabulous">
        </li>
        <li>
            <img src="/images/3.jpg" alt="" title="">
            <input type="button" value="(1)fabulous">
        </li>
        <li>
            <img src="/images/4.jpg" alt="" title="">
            <input type="button" value="(1)fabulous">
        </li>
        <li>
            <img src="/images/5.jpg" alt="" title="">
            <input type="button" value="(1)fabulous">
        </li>
        <li>
            <img src="/images/6.jpg" alt="" title="">
            <input type="button" value="(1)fabulous">
        </li>
        
    </div>
    
</body>
<script>
    function myClick(TagName){
        return document.getElementsByTagName(TagName);
    }

    function getValue() {
        var value = 2;
        return function(){
            this.value = "(" + (value++) + ")fabulous"
        }
    }

    var btns = myClick("input");
    for(var i = 0;i < btns.length;i++){
        btns[i].onclick = getValue();
    }
</script>

5. The role of closures

Closures are useful because they allow functions to be associated with some of the data (environment) they operate on. This is obviously similar to object-oriented programming. In object-oriented programming, objects allow us to associate certain data (object properties) with one or more methods. In some programming languages, such as Java, methods can be declared private, that is, they can only be called by other methods in the same class. JavaScript does not have this native support, but we can use closures to simulate private methods. Private methods are not only conducive to limiting access to code: they also provide a powerful ability to manage the global namespace and avoid non core methods from messing up the public interface part of the code. Let's take the counter as an example, and the code is as follows:

var myCounter = function() {
	var privateCounter = 0;

	function changeBy(val) {
		privateCounter += val;
	}

	return {
		increment: function() {
			changeBy(1);
		},
		decrement: function() {
			changeBy(-1);

		},
		value: function() {
			return privateCounter;
		}
	}
};

var Counter1 = myCounter();
var Counter2 = myCounter();
console.log(Counter1.value()); /* Counter 1 is now 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* Counter 1 is now 2 */
Counter1.decrement();
console.log(Counter1.value()); /* Counter 1 is now 1 */

console.log(Counter2.value()); /* Counter 2 is now 0 */
Counter2.increment();
console.log(Counter2.value()); /* Counter 2 is now 1 */

In the above code, we created an anonymous function with two private items: a variable named , privateCounter , and a function named , changeBy ,. Neither of these items can be accessed directly outside this anonymous function. It must be accessed through three public functions returned by anonymous functions, counter increment,Counter.increment and counter Value, these three public functions share the closure of the same environment. Thanks to the lexical scope of JavaScript, they can access the , privateCounter , variable and , changeBy , function. We store the anonymous function in a variable myCounter , and use it to create multiple counters. Closures will be created at the same time each time. Because each closure has its own lexical environment and each closure refers to the variable , privateCounter , within its lexical scope, the two counters , Counter1 , and , Counter2 , are independent. Using closures in this way provides many benefits related to object-oriented programming -- especially data hiding and encapsulation.

6. Some problems caused by closures

Before ECMAScript 2015 introduced the let keyword, there was a common closure creation problem in the loop. See the following code:

<!DOCTYPE html>
<html>
        <head>
                <meta charset="utf-8">
                <title>official account AlbertYang</title>
        </head>
        <body>
                <p id="help">Prompt information</p>
                <p>E-mail: <input type="text" id="email" name="email"></p>
                <p>Name: <input type="text" id="name" name="name"></p>
                <p>Age: <input type="text" id="age" name="age"></p>
 
        </body>
        <script>
                function showHelp(help) {
                        document.getElementById('help').innerHTML = help;
                }
 
                function setupHelp() {
                        var helpText = [{
                                        'id': 'email',
                                        'help': 'Your email address'
                                },
                                {
                                        'id': 'name',
                                        'help': 'Your Name'
                                },
                                {
                                        'id': 'age',
                                        'help': 'Your age'
                                }
                        ];
 
                        for (var i = 0; i < helpText.length; i++) {
                                var item = helpText[i];
                                document.getElementById(item.id).onfocus = function() {
                                        showHelp(item.help);
                                }
                        }
                }
                setupHelp();
        </script>
</html>

In the above code, we define three prompt messages in the array "helpText", each of which is associated with the ID of the input "in the corresponding document. A # onfocus # event handler is added to the corresponding input in turn through the loop to display help information. After running this code, you will find that it does not achieve the desired effect. No matter which input the focus is on, the information about age is displayed.

This is because the value assigned to , onfocus , is a closure. These closures are composed of their function definitions and the environment captured in the context of setupHelp. The three closures are created in the loop, but they share the same lexical scope in which there is a variable item. Here, because the variable item is declared with var, and because the variable is promoted (item can be used anywhere in the function setupHelp), item has function scope. When the callback of onfocus is executed, item The value of help is determined. Since the loop has been executed long before the onfocus # event is triggered, the variable object item (shared by the three closures) has pointed to the last item of helpText. To solve this problem, there are several ways.

6.1 first: use more closures

function showHelp(help) {
        document.getElementById('help').innerHTML = help;
}

function makeHelpCallback(help) {
        return function() {
                showHelp(help);
        };
}

function setupHelp() {
        var helpText = [{
                        'id': 'email',
                        'help': 'Your email address'
                },
                {
                        'id': 'name',
                        'help': 'Your Name'
                },
                {
                        'id': 'age',
                        'help': 'Your age'
                }
        ];

        for (var i = 0; i &lt; helpText.length; i++) {
                var item = helpText[i];
                document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
        }
}
setupHelp();

This code can be executed normally. This is because all callbacks no longer share the same environment. The makeHelpCallback function creates a new lexical environment for each callback. In these environments, help points to the corresponding string in the 'helpText' array.

6.2 the second method uses anonymous closures

function showHelp(help) {
        document.getElementById('help').innerHTML = help;
}

function setupHelp() {
        var helpText = [{
                        'id': 'email',
                        'help': 'Your email address'
                },
                {
                        'id': 'name',
                        'help': 'Your Name'
                },
                {
                        'id': 'age',
                        'help': 'Your age'
                }
        ];

        for (var i = 0; i &lt; helpText.length; i++) {
                (function() {
                        var item = helpText[i];
                        document.getElementById(item.id).onfocus = function() {
                                showHelp(item.help);
                        }
                })(); // Immediately associate the item of the current loop item with the event callback
        }
}
setupHelp();

6.3 the third method: use the let keyword introduced in ES2015

function showHelp(help) {
        document.getElementById('help').innerHTML = help;
}

function setupHelp() {
        var helpText = [{
                        'id': 'email',
                        'help': 'Your email address'
                },
                {
                        'id': 'name',
                        'help': 'Your Name'
                },
                {
                        'id': 'age',
                        'help': 'Your age'
                }
        ];

        for (var i = 0; i &lt; helpText.length; i++) {
                let item = helpText[i]; //Use let instead of var
                document.getElementById(item.id).onfocus = function() {
                        showHelp(item.help);
                }
        }
}
setupHelp();

Let is used in this instead of var, because let is a variable with block scope, that is, the variables it declares are only valid in the code block ({}). Therefore, each closure is bound with a variable with block scope, which means that no additional closures are required.

6.4 the fourth method: use {forEach() to traverse

function showHelp(help) {
        document.getElementById('help').innerHTML = help;
}

function setupHelp() {
        var helpText = [{
                        'id': 'email',
                        'help': 'Your email address'
                },
                {
                        'id': 'name',
                        'help': 'Your Name'
                },
                {
                        'id': 'age',
                        'help': 'Your age'
                }
        ];

        helpText.forEach(function(text) {
                document.getElementById(text.id).onfocus = function() {
                        showHelp(text.help);
                }
        });
}
setupHelp();

7. Performance

Because closures will save the variables in the function in memory, which consumes a lot of memory, closures cannot be abused, otherwise it will cause performance problems of web pages. If closures are not required for certain tasks, it is best not to use closures. For example, when creating a new object or class, methods should usually be placed in the prototype object rather than defined in the object's constructor. The reason is that as like as two peas, each method is called, the method is reassigned once. That is to say, for every instance object, geName and getMessage are exactly the same content. Each instance is generated, which requires more memory for repeated content. If there are many instances, it will cause huge memory waste. See the following code:

function MyObject(name, message) {
        this.name = name.toString();
        this.message = message.toString();

        this.getName = function() {
                return this.name;
        };

        this.getMessage = function() {
                return this.message;
        };
}

In the above code, we do not take advantage of closures, so we can avoid using closures. Amend as follows:

function MyObject(name, message) {
        this.name = name.toString();
        this.message = message.toString();
}
MyObject.prototype = {
        getName: function() {
                return this.name;
        },
        getMessage: function() {
                return this.message;
        }
};

If we don't want to redefine the prototype, we can modify it as follows:

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;
};

Think: to test whether you understand closures, look at the following two pieces of code and think about their running results? And give your answer in the message area.

Code 1:

var name = "Window";
var object = {
	name: "Object",

	getNameFunc: function() {
		return function() {
			return this.name;
		};
	}
};
console.log(object.getNameFunc()());//Window

Code 2:

var name = "Window";
var object = {
	name: "Object",
	getNameFunc: function() {
		var that = this;
		return function() {
			return that.name;
		};
	}
};
console.log(object.getNameFunc()());//Object

The above two codes examine this point and closure

8 summary

When an internal function accesses a variable of an external function, it uses a chain search method to obtain it. It searches layer by layer from the inside to the outside. When it finds a level 0 scope, it will report an error if it still does not find the variable. This structure is called scope chain. In essence, a closure is a bridge connecting the inside and outside of a function. Local variables are in a function. After the function is used, the local variables will be automatically released. However, after a closure is generated, the use scope chain of the local variables inside will be extended. The function of a closure is to cache data, which is both an advantage and a disadvantage of a closure, This will cause the variable to not be released in time. If you want to cache data, put the data in the middle of the outer function and the inner function. Because closures will cause the variables in the function to be saved in memory, which consumes a lot of memory, so don't abuse closures.  

Reprinted to JavaScript advanced tutorial (5) - this article allows you to understand scope chains and closures