preface

I had written about closures before, thought I knew it, and then I was asked in an interview about my doubts about life. Just understand oneself just think oneself understand just, if want to understand a thing thoroughly, can’t “copy book” (I was copy book before), but dead dig out every knowledge point, a vague will let the whole system collapse. The original address

Ok, now it’s time to start digging. What is a closure?

Closures are functions that can read variables inside other functions. In javascript, for example, only subfunctions inside a function can read local variables, so closures can be understood as “functions defined inside a function.” In essence, closures are a bridge between what’s inside a function and what’s outside it — from Baidu Encyclopedia

Closures are a natural consequence of writing code based on lexical scope. Closures occur when a function can remember and access the lexical scope in which it is located. ———— What you Don’t know about JS (part 1)

What is lexical scope?

Lexical scope

As shown in the figure, each box contains a scope, and when the engine executes console.log() (the statement in the yellow box), it looks for variables scoped from the inside out. Baz has b,c,baz, and bar has B,c,baz. If b is found,c is ignored, and so on, until all statements match the variable. Otherwise, the engine fails to parse and throws an error.

Besides lexical scope, what else?

In fact, scopes include lexical scopes and dynamic scopes. Scopes in JavaScript are lexical scopes (most programming languages are also based on lexical scopes). In the figure above, we can clearly see that all variables of each function can be used or reused across the entire scope of the function (nested functions can use variables of external functions). This is called function scope. So only functions can create scope “boxes”?

Let’s look at the following code:

for(var b=0; b<3; b++){}console.log('b',b) / / 3
Copy the code

In the above code, no functions are declared, so the variable b declared via var is bound to the outer scope, which is global. (For those who do not know about variable improvement, see my article => “Detailed explanation of ES6 temporary dead zone TDZ”), so the above code is equivalent to:

var b;
for(b=0; b<3; b++){}console.log('b',b) / / 3
Copy the code

. The only thing you want is for b to be used in a for loop and then destroyed. Why contaminate the lexical scope? Fortunately, thanks to the human spirit of exploration and the support of several browser parents for the unsound son of JavaScript, ES6 has let and const to complement the block scope. As follows: b is destroyed at the end of the for loop, and there is no variable with the same name in the lexical scope, so an error is reported.

for(let b=0; b<3; b++){}console.log('b',b) // Uncaught ReferenceError: b is not defined
Copy the code

When we understand block scope, we can think of a {} as a block.

Are scope and context the same thing?

The answer is “NO!!” As we have seen from the previous article, scope is determined when a function is defined. The context is simply the reference to this in the function, which is the object that was mounted when the current function was run.

const a=1
function foo(){
    console.log(this.a)
}

const obj={a:2,foo}

foo() // undefined
obj.foo() / / 2
Copy the code

Here’s a tip: why is const a not mounted to window like var? Here’s the secret, iN Javascript Closures: Every hair in [[Scopes]] From Theory to Implementation (which I didn’t read at all while writing this chapter), the const declared A is actually on [[Scopes]].

Loops and closures

A classic interview question

Why does the following code not match the expected output?

// Block 1
for (var i = 0; i < 5; i++) {
    setTimeout((a)= > {
        console.log(i) // Output 5 times 5
    }, 0)}Copy the code

Suppose A: since the task in the setTimeout block goes directly into the event queue, I first changes to 5 after I loops, and then setTimeout is executed. The arrow function in setTimeout will save the reference to I, so five fives will be printed.

A variant:

// Block 2
for (let i = 0; i < 5; i++) {
    setTimeout((a)= > {
        console.log(i) / / 0,1,2,3,4
    }, 0)}Copy the code

If conclusion A is true, then the above equation should also output 5 times 5, but obviously it is not, so conclusion A is not completely true.

Let’s get rid of the loop and write the simplest asynchronous code:

function test(a){
    setTimeout(function timer(){
        console.log(a)
    })
}

test('hello')
Copy the code

We execute test, setTimeout puts the timer function on the event queue, timer retains the scope of the test function (created when the function was defined), test is finished, there are no other tasks on the main thread, timer is dequeued from the event queue, execute timer, Execute console.log(a), and because of the closure, a will retain the previous reference and print ‘Hello’.

That we are back in the title, because the two pieces of code of different statements only, so we assume that B: because in block 1, anonymous function retains external lexical scope, I was in the global scope, the code block 2 due to block scope, so it retains each loop when I reference.

Variation 2:

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

Using IIFE, the variable I is passed to the anonymous function. IIFE creates a new scope. The timer keeps references to the anonymous function I, so they are output in turn.

Variant 3:

// Block 4
for (var i = 0; i < 5; i++) {
    ((a)= > {
        setTimeout(function timer() {
            console.log(i) // Output five fives
        }, 0()})})Copy the code

Unlike variant 2, IIFE does not pass I to anonymous functions. The timer retains a reference to I in the scope chain at global scope.

After verification of the above two variants, hypothesis B is true that, as the scope chain changes, the retained parameter references in the closure change, and the output parameters change.

After the

If there are any errors, please leave a comment in the comment section below.

Recommend the article

  1. In-depth understanding of closure preconditions – scope and lexical scope