JavaScript "Closure"

JavaScript "Closure"

Many powerful JavaScript libraries such as jQuery and Vue.js use the feature of closures to achieve

1.1 Closures in loops

for (var i = 1; i <= 5; i++) {
   setTimeout(function test() {
        console.log(i) //>> 6 6 6 6 6
    }, i * 1000);
}

The original intention is to output "1 2 3 4 5" in turn every second, and the result becomes "6 6 6 6 6". Why is this happening? According to the variable search mechanism on the scope chain, the i in the function body of the first parameter of setTimeout refers to the i in the global scope. When the for loop is completed, the value of i is 6, so it outputs " 6 6 6 6 6 ". The last article said that using bind can achieve the purpose of correct output, and mentioned that in essence, using bind also uses closures. But what is closure, feel it with code. The following code, just a few lines, easily achieve the goal with a closure.

for (var i = 1; i <= 5; i++) {
(function(j) {//Wraps a layer of function in IIFE form, this function is a closure
setTimeout(function test() {//The j in the function body refers to the parameter j of the outer anonymous function
console.log(j); //>> 1 2 3 4 5
}, j * 1000);
})(i);
}

"Oh, the outermost layer of the loop body is wrapped with a layer of IIFE form of function, is this function a closure?" You are half right! First of all, every closure must be a function. Secondly, each inner function needs to reference the parameters/variables in its upper scope. For example, in the above code, the variable j in the IIFE function body refers to the parameter j of the upper scope, that is, the i in the for loop.

1.2 What is a closure

  1. Definition of closure There are many definitions of JavaScript closures. Every book and every author has different descriptions. Although the author thinks that the definition of a function is a closure is the simplest and most straightforward, in fact, I can’t see it. Ten kinds of definitions, I can't remember one of them till now. In view of this, we don't remember these various definitions at all. Just remember that the time when the closure is generated is more practical. During the interview, it is enough to tell the interviewer when the closure is generated: When the inner scope accesses the parameters/variables/functions in the outer function scope, the closure is generated. Let's use code to talk about it:
function func(){//func1 refers to its outer variable a, so func becomes a closure
let a="coffe";
function func1(){
console.log(a);//Accessed the variable a in the outer function func
debugger;//breakpoint
}
func1();
}
func();

Looking at the above figure, Closure appears in the Scope column, so it can be considered that the closure is also a kind of scope. Since closure is also a kind of scope, closure can solve the classic "closure in loop" problem, can the problem be solved by using scope? Let the author think of the keyword let, give it a try, and modify the code at the beginning of this article:

for (var i = 1; i <= 5; i++) {
   {
      let j = i;
      setTimeout(function test() {
           console.log(j) //>> 1 2 3 4 5
       }, j * 1000);
    }
}

Sure enough, wrapping a scope with the let keyword can also solve the problem and achieve the goal like a closure. Therefore, it can be said that a closure is a kind of scope. It copies a set of parameters, variables/functions accessed in the scope of the outer function. This copy is a shallow copy

1.3. What are the benefits of writing it as a closure?

Of course there are benefits! Taking the previous code as an example, the variable a is similar to a private attribute of a high-level language and cannot be accessed and modified by the outer scope of func, only the scope (including nested scope) inside func can be accessed. In this way, the software design can be encapsulated, and a very powerful class library and framework can be designed, such as our commonly used jQuery, AngularJS, and Vue.js. Look at an example of the most common modular package before ES6 appeared:

//Define a module
function module(n) {
//Private attributes
let name = n;
//Private method
function getModuleName() {
return name;
}
//Private method
function someMethod() {
console.log("coffe1989");
}
//Return in the form of an object
return {
getModuleName: getModuleName,
getXXX: someMethod
};
}
let myapp = module("myModule");//Define a module
console.log(myapp.getModuleName()); //>> myModule
console.log(myapp.getXXX()); //>> coffe1989

1.4 Are there any disadvantages to closures?

The garbage collection (GC) rules in javascript are like this: if objects are no longer referenced, or objects refer to each other to form a data island and are not referenced by other objects outside the island, then these objects will be garbage collected by the JS engine Reclaimer; otherwise, these objects will always be kept in memory. Since a closure will reference variables/functions in the scope of the outer function that contains it, it will take up more memory than other non-closure functions. When the outer function is executed and exits the call stack, because the variables in the outer function scope are referenced, they may not be recycled by the garbage collector of the JS engine, which may cause memory leaks. Excessive use of closures will lead to excessive memory usage and even memory leaks.

function A(){
var count = 0;
function B(){
count ++;
console.log(count);
}
return B;//Function B keeps a reference to count
}
var b = A();
b();//>> 1
b();//>> 2
b();//>> 3

The count is a variable in function A. Its value is changed in function B. Each time B is executed, the value of the count is added by 1 on the original basis. Therefore, the count in function A is always stored in memory and is not recycled by the garbage collector of the JS engine after function A exits the function call stack after function A is executed. The solution to avoid memory leaks caused by closures is to delete all local variables that are no longer used or assign them to null before function A is executed and exits the function call stack.

1.5 Introduction to other usage scenarios

1.5.1 Return a new function

function sayHello2(name) {
    var text = "Hello " + name; // Local variable

    var sayAlert = function() {
        console.log(text);
    };

    return sayAlert;
}

var say2 = sayHello2("coffe1989");
say2(); //>> Hello coffe1989

Calling the sayHello2() function returns sayAlert, which is assigned to say2. Note that say2 is a reference variable, pointing to a function itself, not to a variable.

1.5.2 Ways to extend the global object The following use of closures to expand global objects can effectively protect private variables and form a certain degree of encapsulation and persistence.

function setupSomeGlobals() {
//Private variable
var num = 666;
gAlertNumber = function() {//Not declared with var and let keywords, it will become a method of the global object
console.log(num);
};
gIncreaseNumber = function() {
num++;
};
gSetNumber = function(x) {
num = x;
};
}
setupSomeGlobals();
gAlertNumber(); //>> 666
gIncreaseNumber();
gAlertNumber(); //>> 667
gSetNumber(1989);
gAlertNumber(); //>> 1989

The three global functions gAlertNumber, gIncreaseNumber, and gSetNumber point to the same closure because they are declared in the same setupSomeGlobals() call. The closure they point to is a scope associated with the setupSomeGlobals() function, which includes a copy of the num variable. In other words, these three functions operate on the same num variable.

1.5.3 Extend the life of local variables In daily development, the Image object is often used for reporting data statistics. The sample code is as follows:

var report = function(src) {
var img = new Image();
img.src = src;
}
report('http://www.xxx.com/getClientInfo');//Report client information to data

When this code is running, it is found that there are bugs in some low-version browsers, and some data will be reported missing. The reason is that the Image object is a local variable in the report function. After the report function is called, the Image object is immediately reclaimed by the JS engine garbage collector. At this time, it may not have time to issue the http request, so this may cause the request to report data. failure. How to do it? We can use closures to close the Image object to solve the problem of data loss. The code is as follows:

var report = (function() {
var imgs = [];//Persistent in memory
return function(src) {
var img = new Image();
imgs.push(img);//reference local variable imgs
img.src = src;
}
}());
report('http://www.xxx.com/getClientInfo');//Report client information to data