In lesson3, we covered JS scopes. It’s easy to look at closures once you understand JS scopes.
1. The concept of closures
Lexical scope: “The execution of a function depends on the scope of the variable, which is determined when the function is defined, not when the function is called. To implement lexical scoping, the internal state of a JS function object not only contains the code logic of the function, but must also reference the current scope chain. Function objects can be related to each other via scope chains, and variables inside the function body can be stored in the function scope, a feature known as closures.”
2. What closures do
Closures allow functions to access and manipulate variables outside the function. Closures enable functions to access variables or functions as long as they exist in the scope in which the function was declared. Look at a very simple piece of code:
var outerValue = 'New_Name';
function outerFunc() {
console.log(outerValue);
//New_Name
}
outerFunc();
Copy the code
We declare the variable outerValue and function outerFunc in the same scope (global scope in this case), and then execute outerFunc. It can “see” and access the outerValue. You’ve probably written a lot of code snippets like this, but you probably never realized that you were creating a closure.
Let’s look at another familiar example:
var outerValue = 'New_Name';
var laterFun;
function outerFun() {
var innerValue = 'Fresh knowledge';
function innerFun(){
console.log(outerValue);
console.log(innerValue);
}
laterFun = innerFun;
}
outerFun();
laterFun();
//New_Name
// Refresh your knowledge
Copy the code
Statement from above: laterFun = innerFun; The function stores a reference to innerFun in the variable laterFun, which can be called because it is in global scope. Statement: laterFun (); We can’t call innerFun directly in the global scope because it’s in the outerFun function scope.
So we see that closures allow us to execute inner functions after their scope has gone. This is because when you declare an inner function in an outer function, you not only define the declaration of the inner function, but you also create a closure. This closure contains not only the declaration of the inner function, but also all the variables in that scope at the time the inner function is declared. When the inner function is finally executed, the original scope is still accessible through the closure, even though the declared scope is gone. Isn’t this like an inner function that “wraps” variables? That’s why it’s called closure.
Let’s look at another closure example:
let outerFun;// Undefined global function
{
let blockValue = 'New_Name';// block scope variable
outerFun = function () {
console.log(blockValue)//New_Name
}
}
outerFun();
Copy the code
OuterFun is assigned a value within the block, which (and its parent, the global scope) forms a closure. OuterFun has access to variables within the closure wherever it is called. Note that although the program is out of blockValue scope when we call outerFun, we still have access to it. In general, when a scope exits, variables in that scope die. In this case, JS will detect that the function outerFun is defined in the specified block scope, and outerFun can be referenced outside the block scope, so outerFun is allowed to hold access to the block scope. OuterFun, the function inside the closure, affects the closure’s life cycle.
Closures and loops
A lot of people talk about closures with loops, so let’s take a look
for(var i=1; i<=5; i++) {console.log(i);
}
//1, 2, 3, 4, 5
Copy the code
This code is perfectly fine, 1-5 at a time. But with a little spice, it’s a different story:
for(var i=1; i<=5; i++) {setTimeout(() = >{
console.log(i);
},i*100)}//6 6 6 6
Copy the code
This is what we would have liked to see in the console: 1 to 5 printed at 100ms intervals. But this is what happens: six, six, six, six, and six are printed at 100ms intervals. Why did that happen? Where did the six come from? The cycle ends if I is less than or equal to 5. The value of I is 6 when first true, so the output displays the final value of I at the end of the loop.
In fact, a closer look, this is also in line with expectations. The setTimeout callback is executed after the loop ends, so it prints a 6 each time. So what’s causing this code to behave differently than the semantics suggest?
The reason is that we want to get a copy of I for each iteration of the loop. But according to how scope works, although the five functions in the loop are defined separately in each iteration, they are all in a shared global scope (or they are enclosed in a scope), so there is really only one I. How to solve it? Is to introduce more closure scopes. IIFE creates a scope by declaring and immediately executing a function. Take a look at the following code:
for(var i=1; i<=5; i++) { (function() {
setTimeout(() = >{
console.log(i);
},i*100)
})();
}
// 6 6 6 6
Copy the code
This code still outputs a 6 every 100ms, which is also not as expected. This is because although each setTimeout function encloses the scope created by IIFE in each iteration. But the scope is empty, with no substance, and the referenced I is still the same public I. So let’s spice it up:
for(var i=1; i<=5; i++) { (function() {
var j = i;
setTimeout(() = >{
console.log(j);
},j*100)
})();
}
// 1, 2, 3, 4, 5
Copy the code
In IIFE, we define a local variable j to store the value of I, so that each setTimeout function has its own j, and it is closed to maintain access to variable J. The following code improves this with arguments:
for(var i=1; i<=5; i++) { (function(j) {
setTimeout(() = >{
console.log(j);
},j*100)
})(i);
}
// 1, 2, 3, 4, 5
Copy the code
Similarly, using IIFE within an iteration generates a new scope for each iteration, allowing the callback of the delay function to enclose the new scope within each iteration. Each iteration contains a variable with the correct value for us to access.
Having said that, what are the actual development scenarios for closures?
4. Closure usage scenarios
Let’s talk about two common uses of closures :(1) encapsulating private variables and (2) calling back functions. Let’s look at an example of encapsulating a private variable:
function People() {
var age = 0;
this.getAge = function() {
return age;
}
this.grow = function() { age ++; }}var someone = new People();
someone.grow();
console.log(someone.age)
//undefined means we cannot get the value of the variable directly
console.log(someone.getAge());
//1 Can be obtained using the getAge method
var anotherone = new People();
console.log(anotherone.getAge());
//0 AnotherOne has its own age attribute
Copy the code
We create a People constructor and define age inside the constructor. Due to the limitations of THE JS scoping rules, we can only access this variable inside the constructor. To make age accessible to code outside the scope, we define a getAge method that accesses the variable.
Callbacks are another common use of closures where we may need to access external data frequently, so we can create closures that have access to external data. The basic principle of the callback function is similar to the setTimeout we introduced above, so we won’t talk too much about it here.
So those are the main things we need to know about the closing package, do you know? Anyway I know ~~, let’s do some tests together
5. Test
Guess the result of this code:
var outerValue = 'outer';
var laterFun;
function outerFun() {
var innerValue='inner';
function innerFun(param){
console.log(outerValue);
console.log(innerValue);
console.log(param);
console.log(amazing);
}
laterFun = innerFun;
}
var amazing = 'New_Name';
outerFun();
laterFun("so fun");
Copy the code
The result: the console outputs outer, inner, so fun, New_Name. One thing worth mentioning here is that (1) we add a parameter to innerFun, which is also in the closure. (2) The variable amazing appears after the function declaration, but is also included in the closure. The bottom line is that all variables in the enclosing scope are contained in the closure.
Ok, today we review JS here, we will review the knowledge of functions together next time ~
Please do not hesitate to correct any mistakes. Welcome to review old knowledge and climb new steps with me
References:
[1] JavaScript You Don’t Know (Volume 1)
[2] JavaScript Ninja Secrets 2nd Edition
[3] The Definitive JavaScript Guide, 6th edition
[4] The Essence of JavaScript Programming
[5] JavaScript Learning Guide, 3rd Edition