This is the fourth day of my participation in the August Text Challenge.More challenges in August

1. Variable scope

To understand closures, you must first understand variable scope. There are two types of scope in ECMAScript5’s standard: global scope and function scope. The call relationship between the two is:

  • Global variables can be read directly from inside the function;
  • Normally, variables declared inside a function cannot be read from outside the function;
let num = 1;

function test() {
  let n = 2;
  console.log(num); / / 1
}

test();  
console.log(n); // ReferenceError: n is not defined
Copy the code

In practice, we have to get local variables inside functions for various reasons.

The JavaScript language dictates that all variables of the parent object are visible to the child object, and vice versa. Namely, “chain scope” structure. Based on this, we can define a function within the target function, and the child function can normally access the internal variables of its parent function.

function parent() {
  let n = 1;
  function child() {console.log(n); / / 1}}Copy the code

Since the child function can get the local variable of the parent function, the parent function can return the child function directly, thus achieving the purpose of accessing the internal variable of the function under the global scope.

function parent() {
  let n = 1;
  function child() {console.log(n);  / / 1
  };
  return child;
}

let f1 = parent();
f1();
Copy the code

2. Concepts and features of closures

The above example is the simplest way to write a closure: the function child is a closure, so a closure is a “function defined inside a function.” In essence, a closure is a bridge between the inside and outside of a function.

Closures themselves have several other important features:

  • Nested functions within functions;
  • A closure can access internal arguments, variables, or methods of its outer functions.
  • Arguments and variables used in closures are always kept in memory and are not collected by the garbage collection mechanism after the function call ends.
  • The same closure mechanism can create multiple instances of closure functions, which are independent of each other.

3. The classic way to write closures

3.1 Function as return value

The above example can be further reduced to an anonymous function: an anonymous function accesses the inner variable num of its outer function, and the outer function returns that anonymous function, which returns num.

function closure1(){
  let num = 1;
  return function(){
    return num
  }
}

let fn1 = closure1();
console.log(fn1()); / / 1
Copy the code

In this way, we can declare a variable fn1 in the global scope to carry on the num variable, thus achieving the purpose of accessing local variables in the function in the global scope.

In addition to reading local variables within a function, it also keeps these variables in memory and will not be collected by the garbage collection mechanism after the function call ends. Take this example:

function closure2(){
  let num = 2;
  return function(){
    let n = 0;
    console.log(n++,num++); }}let fn2 = closure2();
fn2();  / / 0 2
fn2();  / / 0. 3
Copy the code

Executing the function instance fn2() twice, we can see that the results are slightly different:

  • N++ prints the same twice:

The variable N is the internal variable of the anonymous function. After the anonymous function call, its chunk of memory space will be released normally, that is, reclaimed by the garbage collection mechanism.

  • Num++ two inconsistent outputs:

An anonymous function refers to the local variable num of its outer function. This dependency persists even after the anonymous function is called, so num cannot be destroyed. The next time an anonymous function is called, the result of the last call will continue to be used.

With this feature of closures, you can really do simple data caching. However, you should not abuse closures, which can easily increase memory consumption and lead to memory leaks or performance problems for web pages.

3.1.2 Multiple closure functions are independent of each other

The same closure mechanism can create multiple instances of closure functions that are independent of each other.

Take this simple example:

function fn(num){
  return function(){
    return num++
  }
}
Copy the code

We declare three instances of closure functions separately, passing in different arguments. Then do 1,2,3 times respectively:

function fn(num){
  return function(){
    return num++
  }
}

let f1 = fn(10);
let f2 = fn(20);
let f3 = fn(30);

console.log(f1())  / / 10
console.log(f2())  / / 20
console.log(f2())  / / 21
console.log(f3())  / / 30
console.log(f3())  / / 31
console.log(f3())  / / 32
Copy the code

It can be seen that the first execution of F1 (), F2 (), and F3 () outputs 10, 20, and 30 successively, and the multiple executions are also accumulated on the results of their last execution, without affecting each other.

3.2 Executing functions now (IIFE)

The function is simply returned as the return value, and the actual function call is written elsewhere. So can we have the outer function return the closure call directly?

The answer, of course, is yes: write it in the form of immediate execution functions (IIFE).

Let’s take a look at what IIFE is:

As we all know, the most common way to call a function in JavaScript is to follow the function name with parentheses (). Sometimes we need to call a function immediately after it is defined. But you can’t just put parentheses after a function definition, because that’s a syntax error.

// A syntax error is displayed
function funcName(){} ();Copy the code

The error occurs because the function keyword can be used as either a statement or an expression.

/ / statements
function f() {}

/ / expression
var f = function f() {}
Copy the code

When used as expressions, functions can be defined and called directly with parentheses.

var f = function f(){ return 1} ();console.log(f) / / 1
Copy the code

To avoid parsing ambiguity, JavaScript dictates that if the function keyword appears at the beginning of a line, it should be interpreted as a statement. So if we also want to use the function keyword to declare a function immediately after it is called, we need to make function not appear directly at the beginning of the line and let the engine understand it as an expression. The easiest way to do this is to put it in parentheses.

(function(){ /* code */} ());/ / or
(function(){ /* code */}) ();Copy the code

This is called the “immediate-invoked Function Expression,” or the Immediately Invoked Function, short for IFE.

3.2.1 Output problem of classical cycle of timer setTimeout

Now that you know how to execute functions immediately, let’s take a look at an example of using a for loop to print 1 through 5. So if the following code is used, what is the result?

for (var i = 1; i <= 5; i++) {
  setTimeout( function timer() {
    console.log(i); // 6 6 6 6
  }, 1000 );
} 
Copy the code

The result is definitely 5 6’s. The reason is that the for loop belongs to synchronous tasks, and the setTimeout timer belongs to the macro task category of asynchronous tasks. The JavaScript engine executes the synchronized main thread code first, then the macro task.

So before the setTimeout timer is executed, the for loop has ended with the variable I = 6. The setTimeout timer is then looped five times, and five sixes are output after all of them are executed.

But our goal is to output 1 to 5, which is obviously not enough. Before I formally introduce the immediate execution of functions (IIFE), I’ll mention another approach: the loop variable I is declared using the let keyword.

for (let i = 1; i <= 5; i++) {
  setTimeout( function timer() {
    console.log(i); // 1, 2, 3, 4, 5
  }, 1000 );
}
Copy the code

Why is it ok after the let declaration? Because the essential requirement for loop 1 to 5 output is to remember the value of the loop variable for each loop.

The way let is declared fits the bill. Portal: JavaScript var, let, const features and their differences

IIFE (execute now function)

for (var i = 1; i <= 5; i++) {
  (function(i){
    setTimeout( function timer() {
      console.log(i); // 1, 2, 3, 4, 5
    }, 1000 );
  })(i);
}
Copy the code

Wrap the setTimeout timer function in a closure with an outer anonymous function, and then write it as IIFE: continue wrapping the outer anonymous function in parentheses, and then call after the parentheses, passing in the loop variables as arguments each time. The result of each loop is the closure call: output the value of I; Based on one of the features of the closure itself: it can hold variables or parameters, all conditions are met to output 1 to 5 correctly.

To add a bit more, the current output format is 1 to 5 simultaneously after one second; Well, I guess what about these five numbers every second?

for (var i = 1; i <= 5; i++) {
  (function(i){
    setTimeout( function timer() {
      console.log(i);
    }, i*1000 );
  })(i);
}
Copy the code

You can control the second parameter of each setTimeout timer: the interval time, multiplied by the loop variable I. The effect is as follows:

3.2.2 Functions are passed as parameters to the API

Closures in combination with the IIFE mechanism are also useful for apis that require functions as parameters. Take the sort() method of arrays: array.prototype.sort () supports passing in a comparator function that lets you customize the sorting rules. The comparator function must have a return value. It is recommended to return type Number.

For example, in the following array scenario: we want you to write a mySort() method that sorts array elements in descending order by any specified attribute value. The mySort() method definitely takes two parameters: the array arr to sort and the specified property value property.

The other API is definitely sort(), so instead of passing in a comparator function directly, we use the IIFE version of the closure: the property value is passed as an argument to the outer anonymous function, and the anonymous function returns the comparator function required by the final sort() method.

var arr = [
  {name:"code".age:19.grade:92},
  {name:"zevi".age:12.grade:94},
  {name:"jego".age:15.grade:95},];function mySort(arr,property){
  arr.sort((function(prop){
    return function(a,b){
       return a[prop] > b[prop] ? -1 : a[prop] < b[prop] ? 1 : 0;
    }
  })(property));
};


mySort(arr,"grade");
console.log(arr); 
/* [ {name:"jego",age:15,grade:95}, {name:"zevi",age:12,grade:94}, {name:"code",age:19,grade:92}, ] */
Copy the code

3.3 Encapsulate the private properties and methods of objects

Closures can also be used to encapsulate objects, especially private properties and methods:

We encapsulate an object Person that has a public property name, a private property _age, and two private methods. We cannot access and modify the private _age property directly, but must call its internal closures getAge and setAge.

function Person(name) {
  var _age;
  function setAge(n) {
    _age = n;
  }
  function getAge() {
    return _age;
  }

  return {
    name: name,
    getAge: getAge,
    setAge: setAge
  };
}


var p1 = Person('zevin');
p1.setAge(22);
console.log(p1.getAge()); / / 22
Copy the code

4. Advantages of using closures

4.1 the advantages

4.1.1 Achieve encapsulation to protect the security of variables in the function

Using the closure writing method can save variables in memory, will not be destroyed by the garbage collection mechanism of the system, thus playing a role in protecting variables.

function closure2(){
  let num = 1;
  return function(){
    console.log(num++)
  }
}

let fn2 = closure2();
fn2();  / / 1
fn2();  / / 2
Copy the code

4.1.2 Avoid pollution of global variables

The use of global variables should be avoided in development to prevent unnecessary naming conflicts and invocation errors

/ / an error
var num = 1;
function test(num){
  console.log(num)
}

test();
let num = test(4);
console.log(num);
Copy the code

You can choose to declare variables inside functions and use closures.

In this way, the normal call of variables can be guaranteed and the pollution of global variables can be avoided.

function test(){
  let num = 1;
  return function(){
    return num
  }
}

let fn = test();
console.log(fn());
Copy the code

4.2 disadvantages

4.2.1 Memory Consumption and Memory Leakage

Each time the outer function is run, a new closure is generated, which in turn preserves the inner variables of the outer function, so memory consumption is high.

Solution: Don’t abuse closures.

At the same time, internal variables referenced in closures are saved and cannot be freed, which also causes memory leaks.

Solutions:

window.onload = function(){
  var userInfor = document.getElementById('user');
  var id = userInfor.id;
  oDiv.onclick = function(){
    alert(id);
  }
  userInfor = null;
}
Copy the code

Before the internal closure uses the variable userInfor, it is followed by another variable ID, which is manually assigned to null after using the variable userInfor.