What is an execution context

The concept of execution context varies, with the following descriptions:

  • Description 1: The execution context is the execution environment of a piece of code executed by JavaScript. For example, when a function is called, the execution context of this function will be entered, and the functions such as this, variables, objects and functions used during the execution of this function will be determined.
  • Description 2: An execution context is an abstract concept used to track the runtime environment of your code.
  • Description 3: The execution context is an abstraction of the context in which the current JavaScript code is parsed and executed.

Summary execution context is simply an abstraction of the environment in which code is executed.

The type of execution context

There are three types of execution context in JavaScript:

  • Global execution Context – This is the default or base context, and any code that is not inside a function is in the global context. It does two things: it creates a global Window object (in the case of the browser) and sets the value of this to equal the global object. There is only one global execution context in a program.
  • Function execution Context – Each time a function is called, a new context is created for that function. Each function has its own execution context, but is created when the function is called. There can be any number of function contexts. Whenever a new execution context is created, it performs a series of steps in a defined order.
  • Eval function execution Context – Code executed inside the Eval function also has its own execution context, but since Eval is not often used by JavaScript developers, it will not be discussed here.

Execution stack

An execution stack, or “call stack” in other programming languages, is a stack with LIFO (LIFO) data structures that are used to store all execution contexts created while the code is running.

When the JavaScript engine first encounters your script, it creates a global execution context and pushes it onto the current execution stack. Every time the engine encounters a function call, it creates a new execution context for that function and pushes it to the top of the stack.

The engine executes functions whose execution context is at the top of the stack. When the function completes execution, the execution context pops out of the stack and the control flow moves to the next context in the current stack.

Let’s take a look at the following code example:

var a = 'Hello World! ';

function first() {
  console.log('Inside first function');
  second();
  console.log('Again inside first function');
}

function second() {
  console.log('Inside second function');
}

first();
console.log('Inside Global Execution Context');

// Inside first function
// Inside second function
// Again inside first function
// Inside Global Execution Context
Copy the code

As shown above:

  • When the above code loads in the browser, the JavaScript engine creates a global execution context and pushes it onto the current execution stack. When the first() function call is encountered, the JavaScript engine creates a new execution context for the function and pushes it to the top of the current execution stack.
  • When second() is called from within the first() function, the JavaScript engine creates a new execution context for the second() function and pushes it to the top of the current execution stack. When second() completes, its execution context is popped off the current stack and the control flow moves to the next execution context, that of the first() function.
  • When first() completes, its execution context pops off the stack and the control flow reaches the global execution context. Once all code has been executed, the JavaScript engine removes the global execution context from the current stack.

Using ECStack to simulate the call stack:

ECStack = []
Copy the code

The ECStack is empty only when the entire application terminates, so there is always a globalContext at the bottom of the ECStack:

ECStack.push(globalContext)
Copy the code

Using pseudocode to simulate the above code behavior:

ECStack.push(<first> functionContext); ECStack.push(<second> functionContext); // second out of the stack ecstack.pop (); // first out of the stack ecstack.pop ();Copy the code

To reinforce our understanding of the execution context, let’s draw a simple closure example:

function f1() {
    var n = 99;
    function fn2() {
        console.log(n);
    }
    return fn2;
}
f1()(); / / 99

Copy the code

Using pseudocode to simulate the above code behavior:

ECStack.push(<f1> functionContext); // f1.pop(); ECStack.push(<f2> functionContext); // f2.pop()Copy the code

Because the function f2 in F1 is not called to execute in f1’s executable code, f2 does not create a new context when F1 is executed, and a new context is not created until F2 is executed. As shown below:

ES3 version

There are three important properties in the ES3 release execution context:

  • VO (Variable Object)
  • Scope chain
  • this

Each execution context can be abstracted as an object.

Examples of constituent code for an execution context:

executionContextObj = {
  scopeChain: { /* variableObject + all parent execution context variable objects */ },
  [variableObject | activationObject]: {
    /* Function arguments/ internal variables and function declarations */
    arguments. },this: {}}Copy the code

The variable object

A variable object is a data scope associated with the execution context that stores variable and function declarations defined in the context. Variable objects vary from execution context to execution context:

  • The variable object in the global context is the global object, which in the browser is the Window object. In top-level JavaScript code, you can reference the global object with the keyword this.
  • In the function execution context we refer to a variable object as an activation object (AO). Since a variable object is a specification or engine implementation, it cannot be accessed directly in the JavaScript environment, only when the function is invoked and the variable object is activated as an active object. We can access the properties and methods in it.

An active object is a variable object, but in a different state and phase

The scope chain

Variable queries for scopes and scope chains are implemented by execution context stored in browser memory for javascript. When a variable is searched, it is first searched from the variable object in the current context. If there is no variable object in the parent scope, it is searched up to the variable object in the global context. If there is no variable object, an error is reported. Thus a linked list of variable objects in multiple execution contexts is called a scoped chain.

What’s the difference between scope and execution context?

Function execution context is created when a function is called, before the function body is executed, and is automatically released when the function call ends. Because different calls may have different arguments:

var a = 10;
function fn(x) {
  var a = 20;
  console.log(arguments)
  console.log(x)
}
fn(20)
fn(10) // Different calls may have different arguments
Copy the code

Whereas JavaScript uses lexical scope, the scope created by the FN function is defined when the function is defined

A scope is just a “territory” in which there are no variables, and the values of the variables are retrieved from the corresponding execution context of the scope, so the scope is static and the execution context is dynamic. That is, a scope is only used to define the valid range of variables that you define in that scope.

In the same scope, different calls to the same function will produce different execution contexts, resulting in different values of variables. Therefore, the value of a variable in a scope is determined during execution, while the scope is determined at the time of function creation.

This binding

In the context of global execution, the value of this points to the global object (in browsers, this refers to the Window object).

In the context of function execution, the value of this depends on how the function is called

  • The function is called through an object method, and this refers to the called object
  • This refers to the global object. Strictly, the value of this is undefined
  • Call the function with new, this pointing to the newly created object
  • Calling a function using call, apply, or bind changes the value of this to the first argument passed in, for example
function fn () {
  console.log(this)}function fn1 () {
  'use strict'
  console.log(this)
}

fn() // A normal function call. This refers to the window object
fn() // In strict mode, this is undefined

let foo = {
  baz: function() {
  console.log(this);
  }
}

foo.baz();   // 'this' points to 'foo'

let bar = foo.baz;

bar();       // 'this' points to the global window object because no reference object is specified

let obj {
  name: 'hello'
}

foo.baz.call(obj) // call changes this to point to the obj object

Copy the code

The life cycle

The lifecycle of an execution context consists of three phases:

  • Create a stage
    • Generating variable objects
      • Create the arguments
      • Scanning function declaration
      • Scanning variable declaration
    • Establish scope chains
    • Make sure this points to
  • Execution phase
    • Variable assignment
    • Function reference
    • Execute other code
  • Destruction of phase

Create a stage

Generate variable objects/active objects

  • Create Arguments: If it is a function context, the arguments object is created first, adding the parameter name and value to the variable object
  • Scan function declarations: For found function declarations, the function name and function reference (pointer) are stored in VO, and if VO already has a function of the same name, it is overwritten.
  • Scan variable declarations: For each variable declaration found, introduce the variable name into VO and initialize the value of the variable to undefined. If the variable name already exists in the variable object, no action is taken and the scan continues.

Here’s an example:

function person(age) {
  console.log(typeof name); // function
  console.log(typeof getName); // undefined
  var name = 'abby';
  var hobby = 'game';
  var getName = function getName() {
    return 'Lucky';
  };
  function name() {
    return 'Abby';
  }
  function getAge() {
    return age;
  }
  console.log(typeof name); // string
  console.log(typeof getName); // function
  name = function () {};
  console.log(typeof name); // function
}
person(20);

Copy the code

When calling person(20), the state of the creation is like this before the code executes:

personContext = {
    scopeChain: {... },activationObject: {
        arguments: {
            0: 20.length: 1
        },
        age: 20.name: pointer, // reference to function name(),
        getAge: pointer, // reference to function getAge(),
        hobby: undefined.getName : undefined,},this: {... }}Copy the code

Before the function is executed, a function execution context will be created. First, the reference of the function will be pointed out, and then the variables will be defined in order, initialized as undefined and stored in the AO. When the variable name is scanned, it is found that there is an attribute with the same name in the AO (function declaration variable), so it is ignored

Global execution context creation does not have the create Arguments step

Establish scope chains

During the creation phase of the execution context, the scope chain is created after the variable object. The scope chain itself contains variable objects.

  • When you write a piece of function code, you create a lexical scope. This scope is the property inside the function, denoted by [[scope]], which holds the parent variable object, so [[scope]] is a hierarchy chain.
person.[[scope]] = {
    globalContext.variableObject
}
Copy the code
  • When the function is called, it means that the function is activated, create the function context and press the execution battle, then copy the function [[scope]] property to create the scope chain:
personContext = {
    scopeChain:person.[[scope]]
}
Copy the code
  • Create an active object and push it to the front of the scope chain:
personContext = {
    activationObject: {
        arguments: {
            0: 20.length: 1
        },
        age: 20.name: pointer, // reference to function name(),
        getAge: pointer, // reference to function getAge(),
        hobby: undefined.getName : undefined,},scopeChain:[activationObject,[[scope]]]
}
Copy the code

Make sure this points to

If the current function is called as an object method or a delegate call is made using the BIND, call, apply API, etc., the caller information of the current code block is put into the current execution context, otherwise it defaults to a global object call.

Execution phase

In the execution phase, the execution stream enters the function and runs/interprets the code in the context. The JS engine starts assigning values to defined variables, starts accessing variables along the scope chain, creates a new execution context if there are internal function calls, pushes it onto the execution stack and cedes control

When the code is executed from top to bottom, the activation phase goes like this:

  1. Log is executed for the first time. Name is now a function in the AO. GetName unspecified value in the AO value is undefined.
  2. When the assignment code is executed, getName is assigned to a function expression and name is assigned to Abby
  3. Execute console.log the second time; Name is a string because the function is overwritten by a string assignment and getName is a function.
  4. Execute console.log for the third time. The name is overwritten and therefore of type function

As a result, understanding the execution context provides a nice explanation for variable promotion: the actual position of variable and function declarations in code does not change, but is instead put into memory by the JavaScript engine at compile time

This explains why we can access the name before it is declared, why the type value of the name after it changes, and why getName is undefined when first printed.

✨ ES6 introduces the let and const keywords, allowing JavaScript to have block-level scope like other languages, which solves a number of problems associated with variable promotion.

Destruction of phase

In general, when the function completes, the current execution context is ejected from the execution context stack and waits for the virtual machine to reclaim. Control is returned to the execution context on the execution stack.

ES5 version

The ES5 specification removes variable and active objects from ES3 and replaces them with LexicalEnvironment Component and VariableEnvironment Component.

Why do you need two environment components

In general, an execution context VariableEnvironment and LexicalEnvironment refer to the same LexicalEnvironment. The VariableEnvironment component is used to record the binding and function declarations of var declarations. The LexicalEnvironment component is used to record other declared bindings (such as lets, const, class, and so on).

The main reason to distinguish between the two components is to achieve block-level scope without affecting var declarations and function declarations.

The life cycle

The life cycle of es5 execution context also includes three stages: create stage → execute stage → reclaim stage

Create a stage

The creation phase does three things:

  • Determine the value of this, also known as this Binding
  • The LexicalEnvironment component is created
  • The VariableEnvironment component is created

It might be more intuitive to look at the pseudocode directly:

ExecutionContext = {  
  ThisBinding = <this value>// Make sure this LexicalEnvironment = {... }, // lexical environment VariableEnvironment = {... }, // Variable environment}Copy the code

This Binding

This Binding is bound to the execution context, meaning that there is a This in each execution context. This is not different from This in ES3. The value of This is confirmed at execution time, but not at definition.

Creating Lexical Environment

The ECMAScript specification describes a lexical environment as follows: A lexical environment is a type of specification used to define associations between identifiers and variable and function values within ECMAScript code based on lexical nesting structures. A lexical Environment consists of an Environment Record and a possibly null reference to the outer lexical Environment. In general, lexical environments are associated with specific ECMAScript code syntactic structures, such as functions, code blocks, Catch clauses in TryCatch, and a new lexical environment is created each time such code is executed.

In short, the lexical environment is a representation of the relationship between identifiers and values within the corresponding code block.

The lexical environment is structured as follows:

// Global execution context
GlobalExectionContext = {
 LexicalEnvironment: {  // lexical context
   EnvironmentRecord: { // Environment record
      Type: 'Object'.// Global environment
      outer: <null>, // A reference to the external environment}}}// Function execution context
FunctionExectionContext = {
   LexicalEnvironment: {  // lexical context
   EnvironmentRecord: { // Environment record
      Type: 'Declarative'.// Function environment
      outer:<Global or outer function environment reference>, // References to the external environment}}}Copy the code

You can see that there are two types of lexical environments:

  • Global environment: is a lexical environment with no external environment, whose external environment reference is NULL. You have a global object (window object) with its associated methods and properties, and a user-defined global variable to which the value of this refers.
  • Function environment: Variables defined by the user in the function are stored in the environment record, which contains arguments objects. A reference to an external environment can be to the global environment or to an external function environment that contains internal functions.

The lexical environment has two components:

  • EnvironmentRecord: An identifier binding that records the corresponding code block.

It can be understood that all variable declarations and function declarations (including their parameters if the code block is a function) in the corresponding code block are stored here, corresponding to the variable object or active object in ES3

  • References to external lexical environments (OUTER) : Used to form logically nested structures of multiple lexical environments to achieve the ability to access external lexical environment variables.

The nested structure of the lexical environment logically corresponds to the scope chain in ES3

There are also two types of environmental records:

  • Use declarative environment loggers in functional environments to store variables, functions, and parameters.
  • Object environment loggers are used in the global environment to define the relationship between variables and functions that appear in the global context.

To summarize a little bit:

  • The lexical environment for creating the global context uses the object environment logger, outer null
  • Use declarative environment loggers, outer values global objects, or parent lexical environments to create lexical environments for function contexts

Create variable environment

The variable environment is also a lexical environment, so it has all the attributes of the lexical environment defined above.

The difference is that a VariableEnvironment is used to record the bindings and function declarations of var declarations, while a LexicalEnvironment is used to record the bindings of other declarations (such as lets, const, class, and so on).

Here’s a code to understand:

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

The execution context is as follows:

GlobalExectionContext = {

  ThisBinding: <Global Object>, LexicalEnvironment: {EnvironmentRecord: {Type: "Object", // the identifier is binding here < uninitialized >,} outer: <null>}, VariableEnvironment: {EnvironmentRecord: {Type: "Object", // the identifier is binding here c: undefined, multiply: < func > } outer: <null> } } FunctionExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", }, outer: <GlobalLexicalEnvironment> }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", g: undefined, Arguments: {0: 20, 1: 30, length: 2}, }, outer: <GlobalLexicalEnvironment> } }Copy the code

Execution phase

At this stage, all variables are assigned and the code is executed.

Recovery phase

The execution context goes out of the stack and waits for the VM to reclaim the execution context.

conclusion

  1. The program starts and the global context is created

    1.1 Creating a global contextLexical environment
    • Create an object environment logger that defines the relationship between variables and functions that appear in the global context (handling variables defined by lets and const)
    • Creates an external environment reference with a value of null

    1.2 Creating a global contextThe variable environment

    • Create object environment loggers that record the binding and function declarations of var declarations
    • Creates an external environment reference with a value of null
  2. The function is called and the function context is created

    2.1 To create a function contextLexical environment
    • Create declarative environment loggers that handle variables defined by lets and const.
    • Create an external environment reference with a value of global object or parent lexical environment

    2.2 Creating a function contextThe variable environment

    • Creates a declarative environment logger that stores variables, functions, and arguments. It contains a Arguments object passed to the function and the length of the arguments passed to the function. (Responsible for handling the variable defined by var, the initial value of undefined causes the declaration to improve)
    • Create an external environment reference, value global object, or parent lexical environment

Expand and supplement:

The difference between execution context and scope

The concepts of execution context and scope are often confused as the same concepts, and a distinction is made between them below.

We know that javascript is an interpreted language, and the execution of javascript is divided into two stages: compilation and execution, which do different things.

Compilation phase

  • Lexical analysis
  • Syntax analysis
  • Scope rules are determined

Execution phase

  • Create execution context (also known as “preparation” before execution)
  • Execute function code
  • The garbage collection

As you can see from the above, compilation rules are determined during the JS compilation phase, so the == scope is determined at function definition, not at function invocation, but the == execution context is created at function invocation (before the function is executed).

The most obvious execution context is that the reference to this is determined at execution time, while the scoped variables are determined by the structure of the code being written.

The biggest difference between scope and execution context is that the execution context is determined at run time and can change at any time; The scope is defined at definition time and does not change.

In addition,

A context can be thought of as an invisible but actual object in which all variables are stored;

While scope is more abstract, creating a function creates a scope, whether you call it or not, as long as the function is created, it has its own scope, it has its own “site”.

A scope may contain several contexts; It is also possible that there is never a context (the function is not called to execute); It may have, but after the function is executed, the context is destroyed.

Reference: juejin. Cn/post / 684490… Juejin. Cn/post / 684490… Juejin. Cn/post / 684490… Juejin. Cn/post / 684490…