preface

What is an execution context? I was once asked this question by an interviewer and was so bewildered that I couldn’t say a word. After coming back, I baidu, read is also vaguely understand the state, and did not really understand, although it does not prevent me from rote memorization to cope with the interview ~

Later on, the term “execution context” came up more and more frequently in my learning process, whether it was a book or a blog post, “execution context” was always placed in a high position. I decided to master it once and for all.

After reading about a dozen articles, IT dawned on me that “execution context” is definitely one of the most important concepts in JavaScript, and that understanding “execution context” is a prerequisite for understanding other important concepts such as scopes, closures, promotions, and so on.

This article is a brief summary of my “execution context”, not a very in-depth one, and aimed at the average developer. If you have better insights, feel free to leave them in the comments and help us all grow together.

What is an execution context

In short, the “execution context” is the abstract concept of the environment in which the current JS code is parsed and executed, and any code in JS is run in the “execution context”.

The type of execution context

In JS, the running environment mainly has the global environment and the function environment, in the process of code running, the first to enter the global environment, and when the function is called into the corresponding function environment. The execution context corresponding to the global and function environments is the global execution context and function execution context.

The global execution context is the outermost execution context. Any code that is not in a function is in the global execution context. There is only one global execution context in a program.

Each function has its own execution context, but it is created only when the function is called, and any number of function execution contexts can exist in a program.

The code that runs in the eval function also has its own execution context, and since the eval function is not often used, we won’t talk about it anymore.

Execution context stack

In a JS file, there are often multiple functions to be called, that is to say, JS code running process may produce multiple execution context, so how to manage so many execution context?

Execution context is stored in a stack, which we call an execution context stack.

At the beginning of the code execution, the first to enter the global environment, the overall execution context is created into the stack, after entering a corresponding function when the function is called environment, function execution context is created into the stack at this time, when in the execution context on the top of the stack after all code has been completed, the execution context stack will be the function execution context pop-up (stack), Give control to the previous execution context.

So in an execution context stack, the bottom of the stack is always the global execution context, and the top is the execution context of the currently executing function.

Here’s an example:

function fn2() {
  console.log('fn2')
}

function fn1() {
  console.log('fn1')
  fn2();
}

fn1();
Copy the code

How does the above code behave when executing the context stack?

ECStack=[] */ / the global context is created and merged into the stack ecstack.push (global_EC); // fn1 is called, and the fn1 function context is created and merged into stack ecstack.push (fn1_EC); // fn2 is called in fn1, and the fn2 function context is created and merged into stack ecstack.push (fn2_EC); // ecstack.pop (); // ecstack.pop (); // ecstack.pop (); // The global context is removed from the stack ecstack.pop ();Copy the code

Combined with the following figure to understand more clearly and intuitively:

The lifecycle of the execution context

The life cycle of an execution context is divided into the creation stage and the execution stage. The main work of the creation stage is to generate the variable object, establish the scope chain and determine the this point, while the main work of the execution stage is to assign the variable value and execute other code.

The variable object

Each execution context has an associated variable object on which all variables and functions defined in this execution context reside.

We already know that variable objects are generated during the creation phase of the execution context. There are three main processes for generating variable objects:

  • Retrieves the parameters in the current execution context. This procedure generates Arguments objects and creates properties with parameter names as property names and parameter values as property values.

  • Retrieves the function declaration in the current execution context. This procedure creates properties that take the function name as the property name and reference the memory address of the function as the property value.

  • Retrieves variable declarations in the current execution context. This procedure creates properties with the variable name as the property name and undefined as the property value (if the variable name is the same as the declared parameter name or function name, the variable declaration does not interfere with existing properties).

We can represent variable objects with the following pseudocode:

VO = {Argumnets: {}, ParamVariable: ParamVariable, Function: < Function reference>, Variable: undefined}Copy the code

When the execution context enters the execution phase, the variable object becomes an active object. The variable previously declared is then assigned a value.

Both variable objects and active objects refer to the same object, but at different stages of the execution context.

We can represent live objects with the following pseudocode:

// Function: < Function reference>, Function: < Function reference>, Function: < Function reference>}Copy the code

Here is an example of how variable objects in the execution context change during code execution:

function fn1(x) {
    var y = 666;
    function fn2() {};
    var z = function () {};
}

fn1(1);
Copy the code

When the fn1 function is called, the FN1 execution context is created (the creation phase) and added to the stack, with the variable object as follows:

fn1_EC = {
    VO = {
        Arguments: {
            '0': 1,
            length: 1
        },
        x: 1,
        y: undefined,
        fn2: <function fn2 reference>,
        z: undefined
    }
}
Copy the code

During the execution of the fn1 function code (the execution phase), the variable object becomes an active object. The previously declared variable is assigned a value, and its active object looks like this:

fn1_EC = {
    AO = {
        Arguments: {
            '0': 1,
            length: 1
        },
        x: 1,
        y: 666,
        fn2: <function fn2 reference>,
        z: <function express z reference>
    }
}
Copy the code

For a global execution context, there are only two steps in generating a variable object: retrieving the function declaration in the current context and retrieving the variable declaration in the current context.

In the browser environment, the variable object (global object) in the global execution context is the familiar Window object, through which its predefined variables and functions can be used. Variables and functions declared in the global environment also become the properties of the global object.

Once we understand the generation process of variable objects, we can better understand the internal mechanism of function promotion and variable promotion, for example:

console.log(x); // undefined
fn(); // 666
var x = 888;
function fn() {
    console.log(666);
}
Copy the code

The above code, during the creation phase of the global execution context, retrieves the function declaration and the variable declaration in the context. The function is assigned a specific reference address, and the variable is assigned undefined. So the above code is equivalent to:

function fn() {
    console.log(666);
}
var x = undefined;
console.log(x); // undefined
fn(); // 666
x = 888;
Copy the code

This is the internal mechanism of function promotion and variable promotion.

The scope chain

A scope chain is a hierarchy consisting of a series of variable objects from the current execution context and the upper execution context, which determines the order in which code in each level of execution context accesses variables and functions.

As we already know, execution contexts are divided into creation and execution phases. Context in the implementation of the execution phase, when the need to find a particular function or variable, can be carried in the current context of the variable object (active object) in the search, if not found, will be along the upper execution context variable object to look up, if has been to execute in the context of a global variable object (global object) has not been found, then explaining the function or variable does not exist.

Resolution of identifiers (function and variable names) at code execution is done by searching for the identifier name level by level down the scope chain. The search process always starts at the top of the scope chain (the variable object of the currently executing context is always at the top of the scope chain), and then progresses until the identifier is found (if not, an error is usually reported).

The internal execution context can access everything in the external execution context through the scope chain, but the external execution context cannot access anything in the internal execution context. The links in the execution context are linear and ordered. Each execution context can search for variables and functions in the upper-level execution context, but no execution context can search in the lower-level execution context:

var color1 = 'red'; function changeColor() { let color2 = 'pink'; function swapColors() { let color3 = color2; color2 = color1; color1 = color3; SwapColors (); swapColors(); swapColors(); } // This area can only access color1 changeColor();Copy the code

This points to the

The reference to this is determined when the function is called, that is, when the execution context is created.

There are three main scenarios for this reference, namely this in the global execution context, this in a function, and this in a constructor.

  • In the context of global execution, this refers to the global object:
// In the browser environment, the global object is the window object console.log(this === window); // true a = 666; this.b = 888; console.log(window.a); // 666 console.log(window.b); // 888 console.log(a); // 666 console.log(b); / / 888Copy the code
  • Function this. If the called function is owned by an object, then this refers to that object. If the function is called independently, then the internal this refers to undefined (in non-strict mode, to window) :
var x = 666; function fn() { console.log(this.x); } var obj = { x: 888, fn: fn } fn(); // 666 (fn is called independently, in non-strict mode this refers to window) obj.fn(); // 888 (fn is owned by obj, so this refers to obj)Copy the code
  • In the constructor, this refers to the newly created object instance:
function Person(name, age) { this.name = name; this.age = age; } var DLRB = new Person(' Person ', 18); console.log(dlrb.name); // Dilieba console.log(dlrb.age); / / 18Copy the code

Note that in arrow functions, the reference to this is determined when the function is declared. See this article for details.

The last

This article has a lot of conceptual content and is not easy to understand. If you see it in a fog, it is highly recommended that you read it a few more times (carefully and carefully) until you understand it completely. Otherwise, you will lose the point of reading this article.

If there are any mistakes or inadequacies in this article, you are welcome to correct them in the comments section.

Your thumbs-up is a great encouragement to me! Thanks for reading ~