• The scope chain

Premise: When JavaScript executes a piece of executable code, the corresponding context is created (mainly when the function is executed). After execution, the context disappears automatically.

When a function performs a variable lookup, it first looks for the variable object in the current context. If it does not find the variable object, it looks for the variable object in the parent execution context until it finds the global variable object. Thus a linked list of variable objects in multiple execution contexts is called a scoped chain.

Let’s take a look at how scope chains are created and changed in two phases, the creation and activation of a function.

The scope of a function is determined when the function is defined. This is because there is an attribute [[scoped]] inside the function. When the function is created, all the parent variables are stored in it. [[scoped]] is the hierarchy of all the parent variables, but [[scoped]] is not the full scope chain.

Here’s an example:

function foo() {
    function bar() {... }}Copy the code

When functions are created, their respective [[scoped]] is

foo.[[scope]] = [
  globalContext.VO
];

bar.[[scope]] = [
    fooContext.AO,
    globalContext.VO
];
Copy the code

The active object is added to the front end of the scope when the function is activated, at runtime, and entered into the function context. (Can be roughly understood as internal variables, parameters,)

At this point, the scope chain is created.

  • closure

MDN defines closures as

Closures are functions that can access free variables

So what are free variables

A free variable is a variable that is used in a function but is neither a function parameter nor a local variable of the function

From this we conclude that

Closures = functions + free variables that functions can access

Here’s an example:

const a = 1;
function foo () {
    console.log(a);
}
foo();
Copy the code

So the Little Red Book says: Technically, all JavaScript functions are closures.

Of course, this is a theoretical closure

To better understand the following, let’s first look at what is the JS execution context, and what is the relationship between the execution context and scope

  1. A code block corresponds to an execution context, and the code encapsulated as a function is considered a code block, or the “global scope” is also considered a code block.
  2. When the program runs and enters a code block, a new execution context is created and placed in a stack. When the program runs to the end of the code block, the corresponding execution context is popped out of the stack.
  3. When a program runs in one code block to a point where it needs to switch to another code block (another function is called), the state of the current executable context is set to pending, and a new executable context is generated and placed at the top of the stack.
  4. The executable context at the top of the stack is called a running Execution context. When the top executable context is popped, the last suspended executable context continues execution.
  5. When a function is executed, the execution context object created holds some information about the code’s execution and also stores the current scope in the execution context. So their relationship is just a storage relationship.

As a practical matter, the following functions are closures:

  1. It persists even if the context in which it was created has been destroyed (such as an inner function returning from a parent function)
  2. Free variables are referenced in the code

Don’t say, paste code (code from the little Red Book)

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope();
foo();
Copy the code

This code is briefly executed

  1. Enter the global code, create the global execution context, and push the global execution context onto the execution context stack
  2. Global execution context initialization
  3. Execute the checkScope function, create the CheckScope function execution context, the checkScope execution context is pushed into the execution context stack
  4. Checkscope performs context initialization, creating variable objects, scope chains, this, and so on
  5. When the checkScope function completes execution, the CheckScope execution context pops out of the execution context stack
  6. Execute f function, create f function execution context, f execution context is pushed into the execution context stack
  7. F performs context initialization, creating variable objects, scope chains, this, and so on
  8. When the f function completes execution, the f function context pops out of the execution context stack

To understand this process, we should think about the question, and that is:

When f is executed, the scope of the function is destroyed, and the scope of the function is read.

When we look at the execution, we know that the f execution context maintains a chain of scopes: step 7 of the process

fContext = { Scope: [AO, checkscopeContext.AO, globalContext.VO], }

If f references a value in checkScopecontext. AO, the function can still read the checkscopecontext. AO value, even if the checkscopeContext is destroyed. But JavaScript still makes checkScopecontext. AO live in memory, and f functions can still find it through the scope chain of f functions. It’s because JavaScript does this that it implements the concept of closures.

Let’s do a code to understand this

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0] (); data[1] (); data[2] ();// All outputs are 3
Copy the code

Let’s examine the reason why before the data[0] function is executed, the global context VO is

globalContext = {
    VO: {
        data: [...]. .i: 3}}Copy the code

When data[0] is executed, the scope chain of the data[0] function is

data[0]Context = {
    Scope: [AO, gloabalContext.VO]
}
Copy the code

There is no I value in it, so look it up in GlobalContext.vo. I is 3, so it prints all 3

· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · line · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·

To closure


var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}

data[0] ();/ / 0
data[1] ();/ / 1
data[2] ();/ / 2
Copy the code

When the data[0] function is executed, the scope chain changes

data[0]Context = {
    Scope: [AO, anonymous function context.ao globalContext.vo]}Copy the code

Globalcontext. VO has an I = 3, so it will print 0, 1, 2.

  • Variable ascension

So without saying much, let’s just look at the code

console.log(v1);
var v1 = 100;
function foo() {
    console.log(v1);
    var v1 = 200;
    console.log(v1);
}
foo();
console.log(v1);
// undefined undefined 200 100
Copy the code
  1. All declarations are promoted to the top of the scope.
  2. The same variable is declared only once; others are ignored or overwritten.

Look at the function promotion:

bar()

var bar = function() {
  console.log(1);
}
TypeError: bar is not a function
Copy the code
bar()
function bar() {
  console.log(1);
}
Copy the code
  1. A functional variable formal declaration promotes a variable that has no value just like a normal variable.
  2. Function declarations are promoted slightly differently from variable promotions in that they are promoted to the top of the scope, and the declaration content is promoted to the top with it.
  3. Function declarations take precedence over variable declarations, and the function declaration is promoted along with the part of the function definition.