Closures are closely related to function scope chains, so I’m going to write them together

The scope chain of a function

  • A global Context is called a variable object (it exists at the time of code Execution, not just at the time of function Execution). The local context of a function is called an active object (which exists only during the execution of the function)
  • When calling a function, the Execution Context is first created, and then an active object of the function is created and pushed to the front of the scope chain. This means that the Execution of the function Execution Context has two variable objects. A scope chain is essentially a list of Pointers, each pointing to a variable object
  • Function B, defined internally by function A, adds function A’s live object to its scope chain
  • After a closure is implemented, the live object of an external function cannot be destroyed after its execution because the internal function still has a reference to it and remains in memory.

Closures from the Little Red Book example

let value1 = 5
let value2 = 10

function compare(a,b){
// You need to look at compare's prototype object to see the [[scopes]] properties
/ / because the compare. The prototype. The constructor and foo pointed to the same function, so it opens at the constructor option.
    console.log(compare.prototype)
    if (a>b){
        return 1
    }else {
        return 0}}let result = compare(value1,value2)
Copy the code

  • parsing
  1. When you define compare(), you create a Scope chain for it, preloading the Global variable object in [[Scope]], which is Global and Script in the diagram. That is, even if you don’t call compare, the red circle in the figure above still exists (except that a result is missing).
  2. Call compare: First create the execution context, then copy the function’s [[Scope]] to create its Scope chain, and finally create the function’s active object (used as a variable object) and push it to the front of the Scope chain. (This part of the process is not visible in the figure above [[Scope]])
  3. After the function completes execution, the live object is destroyed, leaving only the [[Scope]] Global variable objects that are stored in the function definition, namely the Global and Script in the diagram.


  • A scope chain is essentially a list of Pointers, each pointing to a variable object



Closure cases in MDN

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

const add5 = makeAdder(5);
const add10 = makeAdder(10);

console.log(add5(2));  / / 7
console.log(add10(2)); / / 12
Copy the code
  • Essentially, a makeAdder is a function factory-it creates a function that adds and sums the specified value and its arguments
  • Add5 and add10 are closures. They share the same function definition, but preserve different lexical environments. In the add5 environment, x is 5. In add10, x is 10.
  • Add5 is the anonymous function itself. If add5 is executed, it will evaluate correctly, which means that add5 can remember and access its lexical scope, and that add5 functions will run outside the current lexical scope.
  • Normally, when the makeAdder function completes, its scope is destroyed and the garbage collector frees that memory space. Closures magically keep the scope of the makeAdder alive, and ADD5 still holds a reference to that scope, which is the closure.


  • annotation

The inner attribute [[Scope]] contains the collection of objects in the Scope in which the function was created

If you don’t know the difference between execution context, variable object, and live object. Please step hereJS Execution environment (EC), Variable Object (VO), Active Object (AO), Scope chain



The principle of closures

Now we can talk about closures

JS Advanced Programming defines a closure as a function that refers to a variable in the scope of another function


  • To illustrate this definition, consider the following example from JS Advanced Programming
function createCompareFun(propertyName){
        return function closure (object1,object2){
            console.log(closure.prototype)
            let name1 = object1[propertyName]
            let name2 = object2[propertyName]

            return name1>name2?1:0}}let compare = createCompareFun(name)
    let result = compare({name:'Nicholas'}, {name: 'Matt'})
Copy the code

Open the console to see closure. Prototype

A function defined inside a function adds its active object containing the function to its scope chain.

  1. After createCompareFun returns the Closure function,
  2. The closure function first copies createCompareFun’s active object and global variable object into itself, so createCompareFun’s active object is the closure in the diagram
  3. The Closure function creates its own live object and pushes it to the front of the scope chain. (This part of the process is not visible in [[Scope]] in the figure above)

Pros and cons of closures

advantages

You can often see closures in some syntax tricks.

  • MDN: “Closures are useful because they allow functions to be associated with some data (environment) on which they operate. This is obviously analogous to object-oriented programming. In object-oriented programming, objects allow us to associate certain data (properties of objects) with one or more methods.”

  • Closures can also store variables in separate scopes as private members, preventing them from contaminating the whole world

disadvantages

In the second example above, when createCompareFun completes, its active object is not destroyed and remains in memory because the closure retains its reference until the closure is destroyed

We can optimize the second example

let compare = createCompareFun(name)
let result = compare({name:'Nicholas'}, {name: 'Matt'})
compare = null
Copy the code

Once a function is dereferenced, the garbage collection mechanism frees its footprint and the scope chain is destroyed (except for the global scope)


This in the closure

Let’s start with two examples

const name = "The Window"
const object = {
    name:'My Object'.getNameFun: function (){
        return function (){
            return this.name
        }
    }
}
let a = object.getNameFun()()
console.log(a) // The Window
Copy the code
const name = "The Window";
const object = {
    name: 'My Object'.getNameFun: function () {
        let that = this  // The difference
        return function () {
            return that.name
        }
    }
};
let a = object.getNameFun()()
console.log(a) // My Object
Copy the code
  • In the first example, getNameFun’s this points to the window object; In the second example, getNameFun’s this points to an Object instance object

  • In fact, the inner function cannot directly access the this of the outer function. To access this in the contain scope, you also need to first save its reference to another variable accessible to the closure.


Application of closures

In fact, you often see closures in higher-order functions

Example in MDN: JS code is event-based – functions executed in response to events.

Add some buttons on the page to adjust the size

//JS
function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

//HTML
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;


Copy the code


Image stabilization function

function debounce(fn,wait){
    let timer;
    return function (){
        let context = this
        let arg = [...arguments]

        if (timer){
            clearTimeout(timer)
        } 

        timer = setTimeout(() = >{
            fn.apply(context,arg)
        },wait)
    }
}
Copy the code


Singleton design pattern

class SocketService {
    ws = null // The socket object connected to the server
    connected = false // Indicates whether the connection is successful
    connect = () = >{}// Define the method to connect to the server
    send = () = >{}// Define the method to send data
}

function getInstance() {
    let instance = null;
    return function () {
        if (instance) {
            instance = new SocketService()
        }
        return instance
    }
}
var getSocket = getInstance()
const socket = getSocket()
Copy the code


Fetching index values using closure traversal (age-old problem)

// Create four execution functions using the for loop
// Each immediately executed function becomes a closure
// Each iteration of I generates a private scope in which the current value of I is stored

for (var i = 0; i < 10; i++) {
    setTimeout(function(){console.log(i)},0) / / 10 10
}
 
for (var i = 0; i < 10; i++) {
    (function(j){
        setTimeout(function(){console.log(j)},0) / / 0-9
    })(i)
}
Copy the code


By defining a module with functions, we expose the operation functions to the outside world and hide the details inside the module.

function module() {
    const arr = []

    function pushNum(val) {
        if (typeof val == 'number') {
            arr.push(val)
        }
    }

    function get(index) {
        return index < arr.length ? arr[index] : null
    }
    return {
        pushNumExport: pushNum,
        getExport: get
    }
}

let module1 = module()
module1.pushNumExport(15)
console.log(module1.getExport(0)) / / 15
Copy the code


Use closures as privileged methods to access private variables

Programming languages, such as Java, support declaring methods private, meaning that they can only be called by other methods in the same class.

JS has no concept of private members; all object attributes are public. But there are private variables, and any variable defined in a function or block can be considered private. Private variables include function parameters, local variables, and other functions defined inside the function.

Privileged methods – public methods that can access private variables. Define all the private variables and functions in the constructor, and create a privileged method that can access the private members, because the privileged method defined in the constructor is really a closure. Once an instance is created using a constructor, it can only be accessed through privileged methods, otherwise there is no way to directly access private variables and functions.

const Counter = (function() {
// Private variables
  const privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
 // Three private methods
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      returnprivateCounter; }}}) ();console.log(Counter.value()); / / 0
Counter.increment();
Counter.increment();
console.log(Counter.value()); / / 2
Counter.decrement();
console.log(Counter.value()); / / 1
Copy the code

In the previous examples, each closure has its own lexical environment; The code above creates just one lexical environment shared by three functions: Counter. Increment, Counter. Decrement, and Counter. Value.

The shared environment is created inside an anonymous function that executes immediately. This environment contains two private items: a variable named privateCounter and a function named changeBy. Neither of these items can be accessed directly outside the anonymous function. It must be accessed through three public functions returned by anonymous functions.

These three public functions are closures that share the same environment.