Functions have their own execution environment, which defines the permissions of variables or functions to access data. Variables in this environment are destroyed when they leave the execution environment.

function add() {
    let a = 1;
    console.log(a); / / 1
}
console.log(a); // ReferenceError: a is not defined

Copy the code

Example A is accessible within the scope of the add() function, but is not accessible outside the scope.

Is there a way to access a outside of add()? *

function add() {
    const a = 1;
    const addOne = function(b) { return b + a; }
    return addOne;
}

const addOne = add();
console.log(addOne(1)); / / 2

Copy the code

When addOne(1) is finished, it is pushed off the stack and the local variable a should be cleaned up. Instead, we call addOne(1) and get 2. Note A is not destroyed after add() is executed, but enters the scope of addOne().

The addOne() function here is called anonymous function or closure.

How do JS closures store external variables? *

The scope chain

In the example above, addOne gets the value of A. We need to find out if it copies the value of A into its scope, or if A is not destroyed at all, but gives addOne access.

function add() {
    let a = 1;
    const addOne = function(b) { return b + a; }
    ++a;
    return addOne;
}
const addOne = add();
console.log(addOne(1)); / / 3

Copy the code

We can see that addOne() changes its execution result after a is captured by addOne, so instead of copying the variable’s value, the closure holds a “reference” to it.

When JS is running, it will allocate memory space for each executing function, we call this space Scope Object. When a function is called, local variables allocated in the function are stored in this scope object. We can’t read the scoped objects directly in code, but the parser uses them behind the scenes as it processes the data.

JS functions are first-class citizens and may have nested relationships when declared, so there will be multiple scope objects at the same time. The scope objects of all functions are managed by the environment stack. Scoped objects in the stack are accessed sequentially, with the current function’s scope being accessed first. If the accessed variable is not in the current scope, the next level of scope is accessed until the Global object is found. ReferenceError is raised if the global scope is accessed without the object. This is called scope chian.

This is very similar to the concept of stereotype inheritance, except that undefined is returned when an attribute cannot be found at the top of the stereotype chain.

Closures can access local variables in upper-level functions because once a variable is captured, it can still be accessed by the closure even after the upper-level function is called off the stack, but its scoped object has not been destroyed.

validation

If the above theory is correct, let’s test it in code.

function add(a) {
    const addB = function(b) {
        console.log(b);
        const addC = function(c) {
            return a + b + c;
        }
        return addC;
    }
    return addB;
}

const addOne = add(1);
const addTwo = addOne(1);
const addThree = addTwo(1);

Copy the code

This example has two layers of nested closures, and we break on line 3 and line 5, respectively. When line 3 is broken, the debuger area shows the Scope object of the addB() function.

As you can see, the closure’s scoped object generates the corresponding object properties to store variables based on the hierarchy of external functions. If we create a local variable a in the addB function that overwrites the captured parts of the add function, the corresponding Closure(add) property will not be generated.

Ghost livestock var keyword

The design of JS scoped objects subverts my previous understanding of programming languages, such as the following code: A is a local variable, but it can be accessed outside the if statement.

function testVar() {
    if (1) {
        var a = 1;
            let b = 2;
    }
    console.log(a); / / 1
    console.log(b); // ReferenceError
}
testVar();
Copy the code

Variables declared by var are promoted to Local, the Local scope of the current function, while variables declared by let are stored in the Block property, which is destroyed when the if statement is finished, so the b variable cannot be accessed outside of if.

Finally, let’s take a look at some interesting code and think about what the output might be.

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {
                console.log(item + ' ' + list[i])
        });
    }
    return result;
}

function testList() {
    var fnlist = buildList([1.2.3]);

    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

testList() 
Copy the code
"item2 undefined"
"item2 undefined"
"item2 undefined"
Copy the code

We use a loop to store three closures into an array. Each loop closure captures the subscript I of the array, and when we retrieve the closure, we print the elements of the array sequentially, but we don’t.

This is because the var keyword is a trick. The I and item variables declared by the var keyword are scoped to the full scope of the buildList function (Local), and their values are overwritten each time through the loop. For variables at the same level, the closure holds only one “reference” to it, so it can only access the latest values of I and item I = 3 and item = item2 when it executes.

If you let both I and item, the variables captured by the closure are stored in the closure’s scoped object’s Block property, which is recreated each time through the loop and held by the array. So, iterating through the array produces the desired result.

item0 1
item1 2
item2 3
Copy the code

conclusion

A closure can access the variables of an external function, even if the variable has left the environment in which it was created, because the external variables are held by the closure’s scoped object. Closures are a feature that enables implicit transfer of data between nested functions.

Reprint is prohibited by Jingxiu Education