Trust me, JavaScript concepts such as scopes and closures will open up when you understand the execution context (🤣 if you still have doubts).

What role does execution context play?

In JavaScript, the code should be executed sequentially, right?

You might think so. But a very simple example of what actually happens to JavaScript code when it’s executed is a few little-known things:

num = 3;
var num;
console.log(num);3 / / output
Copy the code

The num variable was assigned before the declaration, and no error was reported.

This is because var num; With the num = 3; Are tasks that belong to two different phases of code execution — compilation and execution. Compilation happens before execution, so var num; Num = 3;

The declaration statement (var num) being executed first is like “moving” the declaration to the top of the current scope, a process called promotion. Promotion is closely related to the execution context (see below).

The concept of execution context

When a piece of JavaScript code is running, it is actually running in an execution context. The following three types of code create the corresponding execution context:

  • Global execution context: It is the execution context created to run the body of the code, that is, for any code that exists outside of the function.
  • Function execution context: Each function creates its own execution context at execution time.
  • The Eval function executes the contextUse:eval()The function also creates a new execution context.

Okay, so with those types of execution contexts, how are they created?

Create the execution context

There are explicit steps to creating an execution context:

  1. determinethisAs we know itthisBinding.
  2. Create the LexicalEnvironment component.
  3. Create a VariableEnvironment component.

An execution context can be defined in pseudocode as follows:

ExecutionContext = {
    // this
    ThisBinding = <this value>// LexicalEnvironment = {... }, // VariableEnvironment = {... }},Copy the code

To determine this

In the context of global execution, this always refers to a global object. For example, in the browser environment this refers to the window object.

In the context of function execution, the value of this depends on how the function is called; if called by an object, this refers to that object. Otherwise (in browsers) this usually refers to the global object Window or undefined (strict mode).

var name = "window";
let hello = {
    name:"hello".helloThis: function() {
        console.log(this.name);
    }
}
  
hello.helloThis(hello);// window -> hello -> helloThis. By the hello call

let ht = hello.helloThis;
ht();//window -> helloThis, called by window
Copy the code

Create the lexical environment component

A lexical environment is a structure that contains a mapping of identifier variables, where identifiers represent names of variables (functions) that are references to actual objects (including function-type objects) or original values.

Var name = 1; . The identifier is name and the reference is 1.

The lexical environment consists of an environment logger and a reference to the external environment.

  • The environment logger is used to store the actual location of variable and function declarations in the current environment.

  • References to external environments correspond to other external environments that can be accessed. (So the child scope can access the parent scope)

There are two types of execution context: global execution context and function execution context. There are also two types of lexical context:

  • In the global environment is the object environment logger, with no external environment reference (null). It has built-in Object, Array, and so on, prototype functions in the environment logger (associated with global objects, such as the Window Object), and any user-defined global variables, and the value of this points to the global Object.

  • Within the function environment are declarative environment loggers, which store variables defined within the function. And the referenced external environment may be the global environment, or any external function environment that contains this function. It also contains all property methods defined by the user in the function and a arguments object and the length of the arguments passed to the function.

Pseudocode for two lexical environments:

// Global environment
GlobalExectionContext = {
    // lexical context
    LexicalEnvironment: {
        EnvironmentRecord: {
            // Object environment logger
            Type: "Object".// Bind the identifier here
        }
        outer: <null>}}// Function environment
FunctionExectionContext = {
    // lexical context
    LexicalEnvironment: {
        // Environment logger
        EnvironmentRecord: {
            Type: "Declarative".// Bind the identifier here
        }
        // References to the external environment
        outer: < global environment or external function environment containing the function >}}Copy the code

Create variable environment

The variable environment is very similar to the lexical environment. In ES6, the obvious difference between lexical and variable environments is that the former is used to store function declarations and bindings of variables (let/const), while the latter is used only to store bindings of var variables.

The following code:

let a = 20;
const b = 30;
var c;

function multiply(e, f) {
    var g = 20;
    return e * f * g;
}

c = multiply(20.30);
Copy the code

Corresponding pseudocode:

GlobalExectionContext = {
  ThisBinding: <Global Object>, // LexicalEnvironment: {EnvironmentRecord: {Type: "Object", // Store let/const variable binding a: < uninitialized >, b: < uninitialized >, multiply: < func >} outer: <null>}, // VariableEnvironment: {EnvironmentRecord: {Type: "Object", // bind identifier // store and define var variable c: undefined,} outer: <null> } } FunctionExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", Arguments: {0: 20, 1: 30, length: 2}, }, outer: <GlobalLexicalEnvironment> }, VariableEnvironment: {EnvironmentRecord: {Type: "Declarative", var g: undefined}, outer: <GlobalLexicalEnvironment>}}Copy the code

To solve the question

Why is variable promotion closely related to execution context?

When context creation is performed, the var variable is set to undefined, while the let/const variable is uninitialized. So, if we call the var variable before we declare it, we get undefined. If we call a variable defined by let/const, we prompt Cannot access ‘b’ before initialization.

How does closure work?

Closures are a combination of functions and the lexical environment in which they are declared. The lexical environment stores references to the parent lexical environment (scope).

Execute the execution of the context

Runs/interprets function code in an execution context and assigns variable values as the code executes line by line.

It is worth noting that the variables defined by let (only let) will be assigned to undefined at this stage if they are assigned.

Management of execution context (execution stack)

Since each function is executed with a corresponding execution context. How to manage multiple contexts?

The execution stack stores all execution contexts and follows the last in, first out rule… It seems that the code is really much:

var say = function(){
    hello();
}

var hello = function(){
    console.log("Hello,world!");
}

say();
Copy the code
  1. createGlobal execution contextAnd push it onto the execution stack.
  2. Call theSay functionTo createSay functionAnd push it onto the execution stack.
  3. Enter theSay functionInside, calledHello functionTo createHello functionAnd push it onto the execution stack.
  4. Hello functionExecuted, willhelloRemove execution stack
  5. Say functionExecuted, willsayRemove execution stack

To summarize

All right! Finally finished writing, you big guys reward a praise bie~👍