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

  1. 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.
  2. 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.
  3. 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