Closures occur when a function can remember and access its lexical scope, even if the function is executed outside the current lexical scope.

function foo() {
    var a = 1; // A is a local variable created by Foo
    function bar() { // bar is an inner function that is a closure
        console.log(a); // Use a variable declared in the parent function
    }
    return bar();
}
foo(); / / 1
Copy the code

Foo () declares an internal variable, a, that is not accessible outside the function. Bar () is a function inside foo(), and all local variables inside foo are visible to bar, and vice versa. This is javaScript’s unique “scope chain”.

function foo() {
    var a = 1; // A is a local variable created by Foo
    function bar() { // bar is an inner function that is a closure
        console.log(a); // Use a variable declared in the parent function
    }
    return bar;
}
const myFoo = foo();
myFoo();
Copy the code

This code is exactly the same as the code above, except that the inner function bar returns from the outer function before execution. When foo() executes, assigning its return value (that is, the internal bar function) to the variable myFoo and calling myFoo(), it is really just calling the internal function bar() with different identifier references.

After the foo() function is executed, the entire internal scope of foo() is normally destroyed and the memory used is reclaimed. But now foo’s internal scope bar() is still in use, so it will not be reclaimed. Bar () still holds a reference to the changed scope, which is called a closure. This function is called outside of the lexical scope defined. Closures allow functions to continue to access the lexical scope at definition time.

In one sentence: A closure is a function that has access to variables in the scope of another function. The most common way to create closures is to create another function inside one function.

Some common closures

function foo(a) {
    setTimeout(function timer(){
        console.log(a)
    }, 1000)
}
foo(2);
Copy the code

Foo’s internal scope does not disappear after 1000ms, and the timer function retains a reference to foo’s scope. The timer function is a closure.

Timers, event listeners, Ajax requests, cross-window communication, Web Workers, or any other asynchronous or synchronous task that uses a callback function are essentially closures.

Loops and closures

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

This code is supposed to print 0, 1, 2, 3, 4 every second, but it actually prints 5, 5, 5, 5, 5. First of all, to explain where 5 comes from, the condition that the loop ends is that I is no longer less than 5, the value of I was 5 when the condition was first held, so the output shows the final value of I at the end of the loop.

The callback to a delayed function is not executed until the end of the loop. In fact, when the timer is running even though setTimeout(.. 0), all callbacks are still executed after the loop ends. So it prints a 5 each time.

Our expectation is that each iteration will “capture” itself a copy of I at run time. But in reality, according to the principle of scope, although the five functions in the loop are defined separately in their respective iterations, they are all enclosed in a shared global scope, so there is really only one I. That is, all functions share a reference to I.

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

Change the code to the above and it will work the way we want it to. After this modification, using IIFE (execute function now) within each iteration generates a new scope for each iteration, allowing the callback of the delay function to enclose the new scope within each iteration, with a variable with the correct value accessible within each iteration.

for(let i = 0; i < 5; i++) {
    setTimeout(() = > {
        console.log(i);
    }, i * 1000);
}
Copy the code

Using ES6 block-scoped lets instead of var also serves our purpose.

Closures are a very important feature of JavaScript, which means that the current scope can always access variables in the outer scope. Because functions are the only structures in JavaScript that have their own scope, closure creation depends on functions.

Points to be aware of

Prone to memory leaks. Closures carry the function scope that contains them, and therefore take up more memory than other functions. Overuse of closures can lead to excessive memory usage, so use closures with caution.

About this

Use this object in the closure.

The this object is bound to the runtime execution environment based on the function. In a global function, this refers to window, and when a function is called by a method on an object, this refers to that object, but anonymous functions are executed globally, so their this object usually refers to Window. The previous article on understanding this, call, apply, and bind also covered this.

var name = 'The window';

var object = {
    name: 'my Object'.getName: function() {
        return function() {
            return this.name; }}}console.log(object.getName()()); // The window is in non-strict mode
Copy the code
  1. The above code creates a global variable name and an object containing the name attribute, which also contains a methodgetName(), which returns an anonymous function, which returnsthis.name.
  2. Due to thegetNameReturns a function, therefore calledobject.getName()()The function it returns is immediately called. The result is to return The string “The window”, The value of The global name variable.

Why doesn’t the anonymous function get the this object containing the scope? Each function is automatically called with two special variables: this and arguments. The inner function searches for these two variables only up to its live object, so it is never possible to access the variables of the outer function directly.

However, you can give the closure access to this object by storing it in a variable accessible by the closure.

var name = 'The window';

var object = {
    name: 'my Object'.getName: function() {
        var that = this; // Assign this to that
        return function() {
            returnthat.name; }}}console.log(object.getName()()); // my Object
Copy the code

The above code assigns this to that, which is contained in the function. Even after the function returns, that still refers to the object, so call Object.getName ()() to return “my object”.

Arguments has the same problem as this; if you want to access arguments objects in scope, you must save a reference to that object in a variable accessible to another closure.

There are several special cases where the value of this can change unexpectedly. For example, the following code is the result of modifying its previous example.

var name = 'The window';

var object = {
    name: 'my Object'.getName: function() {
        return this.name
    }
}

console.log(object.getName()); // my Object
console.log((object.getName)()); // my Object
console.log((object.getName = object.getName)()); // The window is in non-strict mode

Copy the code
  1. The first one is the normal call, print"My Object"
  2. The second is that the method is parenthesized before it is called, but it is the same as Object. getName, so it is printed as"my Object"
  3. The third is that an assignment statement is executed and then the result of the assignment is called. Because the assignment expression is the function itself,thisPoints to thewindow, printed"The window"

conclusion

  • Closures are functions that have access to variables in the scope of another function.
  • Closures are often used to create internal variables that cannot be modified externally and that can be manipulated through a specified interface.