If you don’t understand, watch this video
“Once and for all what closure is” move your little hands, welcome everyone’s attention – like – favorites. The next post updates JavaScript’s this pointer
closure
Closures have become something of a myth, important, difficult to master, and hard to define. It is a difficult and characteristic feature of the javascript language, and many advanced applications rely on closures.
How to stand in line
To understand closures, you must first understand variable scope. As mentioned in the previous chapter, javascript has two types of scope: global scope and function scope.
var a = 123;
function fn1(){
console.log(a);
}
fn1();/ / 123
Copy the code
In the code above, the function fn1 reads the global variable A.
However, variables declared inside the function cannot be read outside the function
function fn1(){
var a = 123;
}
console.log(a); //Uncaught ReferenceError: a is not defined
Copy the code
In the code above, the variable a declared inside the function fn1 cannot be read outside the function
If for any reason, you need to get local variables inside the function. Normally, this is not possible, but it is possible through workarounds. Inside the function, define another function
function fn1(){
var a = 123;
function fn2(){
console.log(a); / / 123}}Copy the code
In the above code, the function fn2 is inside the function fn1, and all local variables inside fn1 are visible to fn2. But the other way around, local variables inside fn2 are invisible to FN1. This is the javascript language’s unique chain scope structure, in which child objects look up the variables of all parent objects, level by level. Therefore, all variables of the parent object are visible to the child object, and vice versa.
Fn2 can read local variables of Fn1, so if we return fn2, we can read internal variables of Fn1 externally.
function fn1(){
var a = 123;
function fn2(){
console.log(a); / / 123
}
return fn2;
}
var result = fn1();
console.log(result()); / / 123
Copy the code
In the above code, the return value of function fn1 is function fn2. Since fn2 can read the internal variables of fN1, it can obtain the internal variables of Fn1 externally
A closure is a function fn2 that reads variables inside other functions. In the javascript language, only the child functions inside a function can read the internal variables of the parent function, because closures can be understood simply as functions defined inside a function.
The most important feature of a closure is that it can remember the environment in which it was created. For example, fn2 remembers the environment in which it was created, fN1, so fN2 can get internal variables of FN1.
In essence, a closure is a bridge between the inside and outside of a function
The purpose of closures
[1] Read variables inside a function and keep them in memory at all times. That is, a closure can make its birth environment always exist.
Example: Closures cause internal variables to remember the result of the last call
function a() {
var start = 5;
function b() {
return start++;
};
return b;
}
var inc = a();
inc();/ / 5
inc();/ / 6
inc();/ / 7
// Free memory
inc = null;
Copy the code
In the above code, start is the internal variable of function A. With closures (a closure is created when function B inside function A is referenced by a variable outside of function A), the state of start is preserved, and each call is evaluated on the basis of the previous call. As you can see, the closure Inc keeps the internal environment of function A alive. So, a closure can be thought of as an interface to a function’s internal scope.
Why is that? The reason is that inc is always in memory, and its existence depends on A, so it is always in memory and will not be collected by the garbage collection mechanism after the call ends.
In Javascript, if an object is no longer referenced, then the object is collected by GC. If two objects refer to each other and are no longer referenced by a third party, the two objects referred to each other are also reclaimed. Because function A is referred to by B, which in turn is referred to by INC outside of A, this is why function A is not recycled after execution.
[2] Encapsulates the private properties and methods of objects
function Person(name){
var _age;
function setAge(n){
_age = n;
}
function getAge(){
return _age;
}
return {
name:name,
getAge:getAge,
setAge:setAge
}
}
var p1 = Person('mjj');
p1.setAge(18);
p1.getAge();/ / 18
Copy the code
In the code above, the internal variable _age of the function Person, via the closures getAge and setAge, becomes the private variable of the return object P1.
Note that each time the outer function runs, a new closure is generated, which in turn preserves the inner variables of the outer function, so it is expensive. Therefore, you should not abuse closures, which can cause performance problems for your web pages.
Considerations for using closures
- Because closures cause variables in functions to be stored in memory, which can be very memory consuming, you should not abuse closures, which can cause performance problems for web pages and memory leaks in IE. The solution is to remove all unused local variables before exiting the function.
- Closures change the values of variables inside the parent function, outside the parent function. So, if you use a parent function as an object, a closure as its Public Method, and internal variables as its private values, be careful not to arbitrarily change the values of the parent function’s internal variables.
- Closing the Hole once and for all
conclusion
Closures need to satisfy three conditions:
[1] access scope [2] function nesting [3] is called outside the scope
Execute function immediately
implementation
In Javascript, parentheses () are operators that follow a function name to indicate that the function is called. For example, fn() means to call the fn function.
But sometimes you need to call a function as soon as you define it. The Function is called an instant-invoked Function and the full name is IIFE(Imdiately Invoked Function Expression)
Note: The javascript engine dictates that if the function keyword appears at the beginning of a line, it should be interpreted as a function declaration statement
The function declaration statement requires a function name. An error is reported because there is no function name
function (){} ();//Uncaught SyntaxError: Unexpected token (
Copy the code
The solution is to keep function out of the beginning of the line and have the engine interpret it as an expression. The easiest way to do this is to put it in parentheses
Two common ways of writing it
(function(){/* code */} ());/ / or
(function(){/* code */}) ();Copy the code
In both cases, the engine will assume that it is followed by a representation, not a function definition statement, so it avoids errors.
Pay attention to
Note that the semicolon at the end is necessary in both cases. If the semicolon is omitted, an error may be reported when two IIFE are connected.
/ / an error
(function(){ /* code */} ()) (function(){ /* code */} ())Copy the code
There is no semicolon between the two lines of the code above; JavaScript interprets them together, interpreting the second line as an argument to the first line.
Other writing
By extension, any method that allows the interpreter to process function definitions as expressions, such as the following three, will have the same effect.
var i = function(){return 10} ();true && function (){/* code */} ();0.function(){/* code */} (); !function(){ /* code */} (); ~function(){ /* code */} (); -function(){ /* code */} (); +function(){ /* code */} ();new function(){ /* code */ };
new function(){ /* code */} ();Copy the code
use
IIFE is generally used to construct private variables to avoid global contamination.
Next, a requirements implementation is used to more intuitively illustrate the purpose of IIFE. Suppose you have a requirement that each time you call a function, you return a number that is incremented by 1 (the number starts at 0).
[1] Global variables
Normally, we would use global variables to hold the numeric state
var a = 0;
function add(){
return ++a;
}
console.log(a);/ / 1
console.log(b);/ / 2
Copy the code
Variable A, which is really only related to the add function, is declared as a global variable, which is not appropriate.
[2] Custom attributes
It is more appropriate to change variable A to a custom property of the function
function add(){
return ++add.count;
}
add.count = 0;
console.log(add());/ / 1
console.log(add());/ / 2
Copy the code
Some code may reset add.count unintentionally
【 3 】 IIFE
Using IIFE to keep counter variables private is safer and reduces global space contamination
var add = (function (){
var counter = 0;
return function (){
return ++counter;
}
})();
Copy the code
Matters needing attention
If the following code is executed, an error is displayed indicating that a is undefined
var a = function(){
return 1;
}
(function(){
console.log(a());/ / an error}) ();Copy the code
Because there is no semicolon, the browser interprets the code as follows
var a = function(){
return 1; } (function(){
console.log(a());/ / an error}) ();Copy the code
If we add a semicolon, we can’t go wrong
var a = function(){
return 1;
};
(function(){
console.log(a());/ / 1}) ()Copy the code
Wrong understanding of loops and closures
One easy thing to do wrong
function foo(){
var arr = [];
for(var i = 0; i < 2; i++){
arr[i] = function (){
returni; }}return arr;
}
var bar = foo();
console.log(bar[0] ());/ / 2
Copy the code
The reason for the error is that instead of assigning the return value of the function to the array element, the loop simply assigns the function to the array element. This means that when an anonymous function is called, the value of the variable stored in the execution environment found by scope is no longer the index value at the time of the loop, but the index value at the end of the loop
IIFE solves fallible problems
You can use IIF pass-throughs and closures to create multiple execution environments to hold index values for each state of the loop. Because function parameters are passed by value, different execution environments are created when functions with different parameters are called
function foo() {
var arr = [];
for (var i = 0; i < 2; i++) {
arr[i] = (function(j) {
return function (){
return j;
};
})(i);
}
return arr;
}
var bar = foo();
console.log(bar[1] ());/ / 1
Copy the code
or
function foo() {
var arr = [];
for (var i = 0; i < 2; i++) {
(function(i) {
arr[i] = function() {
return i;
}
})(i)
}
return arr;
}
var bar = foo();
console.log(bar[1] ());Copy the code
Block scope
While IIFE is still more complex, block scopes are more convenient
Because the block scope can rebind the index value I to each iteration of the loop, ensuring that it is reassigned with the value at the end of the last iteration of the loop creates an execution environment for each index value
function foo(){
var arr = [];
for(let i = 0; i < 2; i++){
arr[i] = function(){
returni; }}return arr;
}
var bar = foo();
console.log(bar[1] ());/ / 0
Copy the code
quotes
In programming, if the actual result does not match the expected result, the code step by step diagram of the execution environment, you will find that a lot of times you are taking for granted
The 10 forms of closures
From the definition of closures, we know that any means by which an inner function is passed outside of its scope holds a reference to the original scope, and that the function uses closures wherever it is executed. Next, we’ll look at the 10 forms of closures in more detail
The return value
The most common form is when a function is returned as a return value
var fn = function(){
var a = 'mjj';
var b = function(){
return a;
}
return b;
}
console.log(fn()());
Copy the code
Function assignment
One variant is to assign an inner function to an external variable
var fn2;
var fn = function(){
var a = 'mjj';
var b = function(){
return a;
}
fn2 = b;
}
fn();
console.log(fn2());
Copy the code
Function parameters
Closures can be implemented by passing function form as function parameters
var fn2 = function(fn){
console.log(fn());
}
var fn = function(){
var a = 'mjj';
var b = function(){
return a;
}
fn2(b);
}
fn();
Copy the code
IIFE
As you can see from the previous example code, the function fn() is called immediately after the declaration, so IIFE can be used instead. Note, however, that fn2() can only be used in the form of a function declaration statement, not a function expression.
function fn2(fn){
console.log(fn());
}
(function(){
var a = 'mjj';
var b = function(){
returna; } fn2(b); }) ();Copy the code
Loop assignment
One of the most common errors with closures is the error of loop assignment
function foo(){
var arr = [];
for(var i = 0; i < 5; i++){
arr[i] = function(){
returni; }}return arr;
}
var bar = foo();
console.log(bar[0] ());/ / 6
Copy the code
The correct way to write it is as follows
function foo(){
var arr = [];
for(var i = 0; i< 5; i++){
arr[i] = (function(j){
return function test(){
return j;
}
})(i)
}
return arr;
}
var bar = foo();
console.log(bar[0] ());/ / 0
Copy the code
Getter and setter
We store variables to be manipulated inside the function by providing getter() and setter() functions to prevent them from being exposed externally
var getValue,setValue;
(function(){
var secret = 0;
getValue = function(){
return secret;
}
setValue = function(v){
if(typeof v === 'number'){ secret = v; }}}) ();console.log(getValue());/ / 0
setValue(1);
console.log(getValue());/ / 1
Copy the code
The iterator
We often use closures to implement an accumulator
var add = (function(){
var counter = 0;
return function (){
return ++counter;
}
})();
console.log(add());/ / 1
console.log(add());/ / 2
Copy the code
Similarly, using closures makes it easy to implement an iterator
function setup(x){
var i = 0;
return function (){
returnx[i++]; }}var next = setup(['a'.'b'.'c']);
console.log(next());//'a'
console.log(next());//'b'
console.log(next());//'c'
Copy the code
Distinguish between the first time
var firstLoad = (function(){
var _list = [];
return function(id){
if(_list.indexOf(id) >= 0) {return false;
}else{
_list.push(id);
return true; }}}) (); firstLoad(10);//true
firstLoad(10);//false
firstLoad(20);//true
firstLoad(20);//false
Copy the code
Caching mechanisms
The performance of the function is improved by adding a caching mechanism to the closure so that the same parameters do not need to be counted twice
The code before caching is added is as follows
var mult = function (){
var a = 1;
for(var i = 0; i < arguments.length; i++){
a = a * arguments[i];
}
return a;
}
console.log(mult(1.1.1.2.3.3));/ / 18
Copy the code
After the caching mechanism is added, the code is as follows
var mult = function(){
var cache = {};
var calculate = function(){
var a = 1;
for(var i = 0,len = arguments.length; i<len; i++){
a = a * arguments[i];
}
return a;
};
return function(){
var args = Array.prototype.join.call(arguments.', ');
if(args in cache){
return cache[args];
}
return cache[args] = calculate.apply(null.arguments);
}
}()
console.log(mult(1.1.1.2.3.3));/ / 18
Copy the code
Img object
Img objects are often used for data reporting
var report = function (src){
var img = new Image();
img.src = src;
}
report('http://xx.com/getUserInfo');
Copy the code
However, in some older browsers, the report function loses about 30% of the data, meaning that the report function does not always successfully initiate an HTTP request
The reason is that img is a local scope in the report function. When the report function is called, the img local variable is destroyed immediately, and the HTTP request may not be sent at this time, so the request will be lost
You can now seal the IMG variable in a closure to solve the problem of request loss
var report = (function(){
var imgs = [];
return function(src){
var img = new Image();
imgs.push(img);
img.src = src;
}
})()
report('http://xx.com/getUserInfo');
Copy the code