A, closures

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(); / / 123Copy 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

1\. function fn1(){
2\.     var a = 123;
3\. }
4\. 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()); / / 123Copy 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. (Making a timer)

Example: Closures cause internal variables to remember the result of the last call

Be careful of memory leaks when using closures

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 p1 = null; // Be sure to empty it after useCopy 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. After each complex function is called, a new closure will be formed. The variables in the complex function will always be in memory, which is equivalent to caching. Be careful of memory consumption.

  4. www.jianshu.com/p/26c81fde2…

conclusion

Closures need to satisfy three conditions:

[1] access scope [2] function nesting [3] is called outside the scope

Execute the 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 */}()); 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); / / 2Copy 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()); / / 2Copy 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

Using immediate execution functions, you can set the properties of your roommate while reducing pollution of global variables

Matters needing attention

If the following code is executed, an error is displayed indicating that a is undefined

vvar a = function(){ return 1; } (function(){ console.log(a()); / / 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()); / / 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

1. The easy thing to do wrong

function foo(){ var arr = []; for(var i = 0; i < 2; i++){ arr[i] = function (){ return i; } } return arr; } var bar = foo(); console.log(bar[0]()); / / 2Copy 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

2. IIFE solves error-prone 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]()); / / 1Copy 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

3. Use the let block scope to solve the problem

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(){ return i; } } return arr; } var bar = foo(); console.log(bar[1]()); / / 0Copy the code

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

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

2. Function assignment

Assigns the current closure function to an external function

It’s an alteration that takes the form of assigning an internal function to an external variable

var fn2;
var fn = function () {
    var name = 'mjj';
    var b = function () {
        return name;
    }
     fn2 = b;
}
fn();
console.log(fn2());
Copy the code

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

4, IIFE

Evolution from function parameters

        function fn3(f) {
            console.log(f());
        }

        (function () {
            var name = 'mjj';
            var a = function () {
                return name;
            }
            fn3(a)
        })();
Copy the code

5. Loop assignment

function foo(){ var arr = []; for(var i=0; i<10; i++){ /* (function(i){ arr[i] = function(){ return i } })(i); */ arr[i] = (function(n){ return function(){ return n ; } })(i); } return arr; } var bar = foo(); console.log(bar[1]()); //1 console.log(bar[2]()); //2 console.log(bar[3]()); / / 3Copy the code

Getters and setters

Use closures to encapsulate private attributes and method variables.

We store variables to be manipulated inside the function by providing getter() and setter() functions to prevent them from being exposed externally.

var getter,setter; Function (){var num = 0; // function(){var num = 0; getter = function(){ return num; } setter = function(x){ /* if(typeof x === 'number'){ num = x; } */ num = x; }})(); console.log(getter()); //0 setter(10); console.log(getter()); / / 10Copy the code

7. Iterators (counters)

We often use closures to implement an accumulator

        var add = (function(){
            num = 0;
            return function(){
                return ++num;
            }
        })();
        console.log(add());
        console.log(add());
Copy the code

Similarly, using closures makes it easy to implement an iterator

var color = ['red','blue','yellow','black']; function a(arr){ num = 0; return function(){ return arr[num++]; } } var b = a(color); console.log(b()); //red console.log(b()); //blue console.log(b()); //yellow console.log(b()); //blackCopy the code

8. Distinguish the first time

When the encapsulated function is first called, you expect the return value to be true. Otherwise, it is false.

var first = (function(){ var list = []; Return function(id){if(list.indexof (id) >= 0){// If (list.indexof (id) >= 0){return false; }else{ //indexOf < 0; The character length is less than 0, indicating the first call to list.push(id); return true; }}}) (); console.log(first(10)); //true console.log(first(10)); //false console.log(first(20)); //true console.log(first(30)); //trueCopy the code

9. Caching mechanism

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

9.1 The code before the caching mechanism is added is as follows
var x = function(){ var num = 0; for(var i = 0; i<arguments.length; i++){ num = num + arguments[i]; } return num; } the console. The log (x (1, 2, 3, 4)); The console. The log (x (1, 2, 3, 4));Copy the code
9.2 After the caching mechanism is added, the code is as follows

Simulates the key of an object to see if there is the same key in the object, and returns value if there is

var mult = ( function () { var cache = {}; var calculate = function () { var num = 1; //arguments are passed arguments for(var I = 0; i < arguments.length; i++){ num = num + arguments[i]; } return num; } return function(){ var args = Array.prototype.join.call(arguments,','); if(args in cache){ return cache[args]; } return cache[args] = calculate.apply(null,arguments); }}) (); The console. The log (mult,1,1,2,3,3 (1));Copy the code

10, IMG object image report

Img objects are often used for data reporting (new Image() 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