Before we look at scope chains and closures, what is scope?

Scope is the area in the program where variables are defined, and the location determines the lifetime of the variables. Commonly understood, scope is the accessible scope of variables and functions, that is, scope controls the visibility and life cycle of variables and functions.

Prior to ES6, ES had only two scopes: global scope and function scope.

Objects in a global scope can be accessed anywhere in the code, and their life cycle follows that of the page. A function scope is a variable or function defined inside a function and can only be accessed inside the function. After the function is executed, the variables defined inside the function are destroyed.

E6 introduces the concept of block-level scope: a piece of code wrapped in a pair of braces, such as a function, a judgment statement, a loop statement, or even a single {} statement, can be considered a block-level scope.

Before we look at scope chains, let’s look at the following code:

function bar() {
    console.log(myName)
}
function foo() {
    var myName = Geek Bang
    bar()
}
var myName = "Geek Time"
foo()
Copy the code

What does the bar and foo functions in this code print out?

When this code is executed inside the bar function, its call stack state diagram looks like this:

As you can see from the figure, both the global execution context and the execution context of foo contain the variable myName, so which value of myName should be selected in the bar function?

Your first instinct might be to look for variables in the order they are called on the stack, as follows: first look for myName at the top of the stack, but there isn’t, so look for variables in foo below. When the myName variable is found in the foo function, the myName in the foo function is used.

If you look up variables this way, the final result of executing the bar function should be “Geekbang”. But that’s not the case, and if you try the code above, you’ll find that it prints out as “geek time.” Why is this the case? To explain this, you need to understand the scope chain first.

The scope chain

In the variable environment of each execution context, there is an external reference to the external execution context, which we call outer.

When a piece of code uses a variable, the JavaScript engine first looks for the variable in the current execution context. For example, if the myName variable is not found in the current context, The JavaScript engine then continues to look in the execution context that Outer points to. To get an idea, take a look at this:

As you can see from the figure, the outer of both bar and foo points to the global context, which means that if an external variable is used in bar or foo, the JavaScript engine will look in the global execution context. We call this chain of lookup the scope chain.

Now that we know that variables are looked up through the scope chain, one question remains: why is the external reference to the bar function called by foo the global execution context instead of foo’s execution context?

To answer this question, you also need to know what lexical scope is. This is because during JavaScript execution, its scope chain is determined by lexical scope.

Lexical scope

There are two main modes in which scopes work:

  • The first is the most common lexical scope used by most programming languages.
  • The other is called dynamic scope.

JavaScript uses lexical scope, which means that the scope is determined by the position of the function declaration in the code, and it predicts how the code will look for identifiers during execution.

To understand:

As you can see from the figure, the lexical scope is determined by the position of the code, where the main function contains the bar function, and the bar function contains the foo function. Because the JavaScript scope chain is determined by the lexical scope, the order of the entire lexical scope chain is: Foo function scope – >bar function scope – >main function scope – > global scope.

Now that we know about lexical scopes and scope chains in JavaScript, let’s go back to the question above: in the initial code, function foo called function bar, so why is the external reference to function bar the global execution context instead of function foo’s?

This is because, according to the lexical scope, the parent scope of the bar function is the global scope, and the bar function uses a variable that it does not define, so it looks up in the global scope. That is, the lexical scope is determined at compile time, regardless of how the function is called.

closure

Now that we know about scope chains, we can talk about closures. Once you understand the concepts of variable environments, lexical environments, and scope chains, it becomes much easier to understand what closures are in JavaScript. You can use this code to understand closures:

function foo() {
    var myName = "Geek Time"
    let test1 = 1
    const test2 = 2
    var innerBar = {
        getName:function(){
            console.log(test1)
            return myName
        },
        setName:function(newName){
            myName = newName
        }
    }
    return innerBar
}
var bar = foo()
bar.setName(Geek Bang)
bar.getName()
console.log(bar.getName())
Copy the code

First let’s take a look at the stack when executing the innerBar line inside foo. You can see the following figure:

As you can see from the code above, the innerBar is an object that contains two methods, getName and setName. Both methods are defined inside foo, and both use myName and test1 variables inside.

According to the rules of lexical scope, the inner functions getName and setName always have access to variables in their outer function fooSo when the innerBar object is returned to the global variable bar, the getName and setName functions can still use the myName and test1 variables in foo even though foo has finished executing. So when foo completes, its entire stack looks like this:

As you can see from the figure above, the execution context of foo is popped off the top of the stack, but the setName and getName methods returned use the myName and test1 variables inside foo, so they remain in memory. This is much like the setName and getName methods, which carry the foo function’s own backpack wherever they are called.

It is a specific backpack because it is inaccessible from anywhere except the setName and getName functions, so we can call the backpack the closure of the foo function.

Now we can finally give closure a formal definition. In JavaScript, according to the rules of lexical scope, an inner function can always access the variables declared in its outer function. When an inner function is returned by calling an outer function, the variables referenced by the inner function remain in memory even after the outer function has finished executing. Let’s call this set of variables a closure. For example, if the external function is foo, then the set of variables is called the closure of the foo function.

Definition in the Little Red Book: Closures are functions that refer to variables in the scope of another function, usually implemented in nested functions.

Rule of thumb for identifying closures: If you see a foreign variable in a function (not defined inside the function), then the function is most likely a closure because the foreign variable is caught.

Reference: juejin. Cn/post / 687482…