Execution context

The execution context can be understood as the execution environment of the current code. The same function executed in different environments will produce different results due to different data access.

There are three execution contexts:

  • Global execution context: Only one, created the first time the program runs, which creates a global object in the browser (windowObject), sothisPoint to this global object
  • Function execution context: A function is created when it is called, and a new execution context is created for that function with each call
  • Eval function execution context: runevalThe execution context created when code in a function is used sparsely and is not recommended

Execution context stack

An Execution Context stack (ECS), also known as a call stack, is a stack with LIFO (LIFO) data structures used to store the Execution context created during code Execution

Since JS is single-threaded and can only do one thing at a time, by this mechanism we can keep track of which function is executing while other functions queue up in the call stack.

When the JS engine executes the script for the first time, it will create a global execution context and push it to the top of the stack. Then, with each function call, it will create a new execution context and put it into the top of the stack. After the function is executed, it will be popped up by the execution context stack until it returns to the global execution context.

Code example 🌰

var color = 'blue';

function changeColor() {
  var anotherColor = 'red';
  
  function swapColors() {
    var tempColor = anotherColor;
    anotherColor = color;
    color = tempColor;
  }
  
  swapColors();
}

changeColor();

console.log(color); // red
Copy the code

The execution can be seen in devTool’s Call Stack, where Anonyomus is the global context stack; The rest is the function context stack

Illustration:

Execution process:

  1. I first createdGlobal execution context, pushed into the execution stack, where the executable code begins execution.
  2. And then callchangeColorThe JS engine stops executing the global execution context and activates the functionchangeColorCreates its own execution context, and puts the function context at the top of the execution context stack, where the executable code begins execution.
  3. changeColorCall theswapColorsFunction, pausedchangeColorThe execution context of theswapColorsThe new execution context of the function, and puts the function execution context at the top of the execution context stack.
  4. whenswapColorsWhen the function completes execution, its execution context is pushed off the stack and back upchangeColorExecution continues in the execution context.
  5. changeColorNo executable code, no other execution context encountered, pushed its execution context off the stack and backGlobal execution contextTo continue the execution.
  6. Once all code has been executed, the JS engine is removed from the current stackGlobal execution context.
Note: when a return is encountered, it terminates the execution of the executable code, and therefore pops the current context directly off the stack.Copy the code

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(<changeColor> functionContext);
ECStack.push(<swapColors> functionContext);

/ / swapColors stack
ECStack.pop();
/ / changeColor stack
ECStack.pop();
Copy the code

To reinforce our understanding of the execution context, let’s plot the evolution of a simple closure example.

function f1() {
  var n = 999;
  function f2() {
    console.log(n);
  }
  return f2;
}
f1()() / / 999
Copy the code

Using pseudocode to simulate the above code behavior:

ECStack.push(<f1> functionContext);
/ / f1 stack
ECStack.pop();

ECStack.push(<f2> functionContext);
/ / f2 stack
ECStack.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. The specific evolution process is as follows.

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. All global variables and functions exist as properties and methods of the window.
    console.log(this) //window
    var a=1 // The property attached to the window
    console.log(window.a) / / 1
    console.log(this.a) / / 1
Copy the code
  • In the context of function execution, we use the activation object AO (AO) to represent a variable object. Because a variable object is a specification or engine implementation, it cannot be accessed directly in the JavaScript environment, only when the function is called 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.Copy the code

The scope chain

For JavaScript, variable queries for scopes and scope chains are implemented through an execution context stored in browser memory. 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.

You can refer to my other article on scopes and scope chains: Mastering JavaScript Interviews: What is a Closure?

What is the difference between scope and execution context 🎉 :

A function execution context is created when a function is called, before the function body code executes, 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

JavaScript, on the other hand, uses lexical scope. The scope created by the FN function is defined when the function is defined.

Associated 🎉 :

A scope is just a “territory” where there are no variables and the values of the variables are obtained through the execution context corresponding to the scope, so the scope is statically conceptualized, while 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.

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 the variable object 🎉

  1. Create arguments: If it is a function context, it is created firstargumentsObject to add the parameter name and value to the variable object.
  2. Scan function declarations: For found function declarations, store the function name and function reference (pointer)VO, if theVOOverride (overwrite the reference pointer) if a function with the same name already exists in.
  3. Scan variable declarations: For each variable declaration found, store the variable nameVO, and initialize the value of the variable toundefined. If the variable name already exists in the variable object, no action is taken and the scan continues.

Let’s cite a chestnut to illustrate 🌰 :

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 person(20) is called, but the code has not yet been executed, the created state looks like this:

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, it will create a function execution context first, first point out the reference of the function, then define the variables in order, initialize them as undefined and store them in VO. When the variable name is scanned, it is found that there is an attribute with the same name in VO (function declaration variable), so it is ignored.

Global execution context creation does not have the create Arguments stepCopy the code

Establish the scope chain 🎉

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

  1. When writing a piece of function code, we create a lexical scope. This scope is the property inside the function that we use[[scope]]It holds the parent variable object, so[[scope]]It’s a chain of levels.
person.[[scope]] = [
     globalContext.variableObject
]
Copy the code
  1. When the function is called, it means that the function is activated. Create the function context and push it onto the execution stack. Then copy the function [[scope]] property to create the scope chain:
personContext = {
     scopeChain:person.[[scope]]
}
Copy the code
  1. Create the active object (the generate variable object step above), and then push the active object (AO) 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.

For details on the implementation of this, please refer to my other article: Unlocking this, Apply, Call, bind new positions 🍊

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. First executionconsole.log; At this timenameVOWhere is the function.getNameNo value is specified inVOThe value isundefined.
  2. Execute to the assignment code,getNameIs assigned to a function expression,nameTo be an assignmentabby
  3. Second executionconsole.log; At this timenameBecause the function is overridden by string assignment, sostringtypegetNamefunctionType.
  4. Third executionconsole.log; At this timenameIt’s covered again so it’sfunctiontype

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.

The function execution context for the final console execution:

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

Destruction of phase

In general, when the function completes, the current execution context (local environment) will be ejected from the execution context stack and wait for the virtual machine to reclaim, and control will be returned to the execution context on the execution stack.

Complete sample

Example 1 🌰 :

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

Execute the global code, generate the global context, and push the execution stack

ECStack=[
     globalContext
]
Copy the code

2. Global context initialization

globalContext={
     variableObject: [global,scope,checkscope],
     this:globalContext.variableObject,
     scopeChain:[globalContext.variableObject]
}
Copy the code

3, Create checkScope function generate internal attribute [[scope]], and store the global context scope chain in it

checkscope.[[scope]] = [
     globalContext.variableObject
]
Copy the code

4, call the checkScope function, create the function context, press the stack

ECStack=[
     globalContext,
     checkscopeContext
]
Copy the code

5. At this time, the checkScope function has not been executed, and the execution context is entered

  • The copy function [[scope]] property creates the scope chain
  • Create an active object with the Arguments property
  • Initializes a variable object, adding variable declarations, function declarations, and parameters
  • The live object is pressed into the top of the scope chain
    checkscopeContext = {
        activationObject: {
            arguments: {
                length: 0
            },
            scope: undefined.f: pointer, // reference to function f(),
        },
        scopeChain: [activationObject, globalContext.variableObject],
        this: undefined
    }
Copy the code

6, checkscope function execute, set the value of the variable scope

    checkscopeContext = {
        activationObject: {
            arguments: {
                length: 0
            },
            scope: 'local scope'.f: pointer, // reference to function f(),
        },
        scopeChain: [activationObject, globalContext.variableObject],
        this: undefined
    }
Copy the code

The f function is created to generate the [[scope]] property and hold the scope chain of the parent scope

f.[[scope]]=[
     checkscopeContext.activationObject,
     globalContext.variableObject
]
Copy the code

7, F function call, generate F function context, stack

ECStack=[
     globalContext,
     checkscopeContext,
     fContext
] 
Copy the code

8. Initialize the execution context before f function is executed

  • The copy function [[scope]] property creates the scope chain
  • Create an active object with the Arguments property
  • Initializes a variable object, adding variable declarations, function declarations, and parameters
  • The live object is pressed into the top of the scope chain
fContext = {
     activationObject: {
            arguments: {
                length: 0}},scopeChain: [fContext.activationObject, checkscopeContext.activationObject, globalContext.variableObject],
        this: undefined
    }
Copy the code

9, f function executes, along the scope chain to find the scope value, return scope value

10. After f function is executed, f function context pops up from execution context stack

ECStack=[
     globalContext,
     checkscopeContext
]
Copy the code

11, The checkScope function is executed, the checkScope execution context is popped from the execution context stack

ECStack=[
     globalContext
]
Copy the code

Example 2 🌰 :

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();
Copy the code
  1. Executes the global code, generates the global context, and pushes the execution stack
  2. Global context initialization
  3. createcheckscopeFunction to generate internal properties[[scope]]And store the global context scope chain into it
  4. callcheckscopeFunction, create function context, push
  5. At this timecheckscopeThe function has not been executed. The execution context is entered
    • Copy the function[[scope]]Property creates a chain of scopes
    • withargumentsProperty to create an active object
    • Initializes a variable object, adding variable declarations, function declarations, and parameters
    • The live object is pressed into the top of the scope chain
  6. checkscopeFunction execution, on variablesscopeSet value,fThe function is created and generated[[scope]]Property and saves the scope chain of the parent scope
  7. Return function f, then the checkScope function is completed, popstack
  8. fFunction call, generatefFunction context, pushing
  9. At this timefThe function has not been executed, initializes the execution context
    • Copy the function[[scope]]Property creates a chain of scopes
    • withargumentsProperty to create an active object
    • Initializes a variable object, adding variable declarations, function declarations, and parameters
    • The live object is pressed into the top of the scope chain
  10. fFunction executes, looking up along the scope chainscopeValue, returnscope
  11. fWhen the function completes,fThe function context pops up from the execution context stack

🚀 the only difference is that the checkScope function completes the stack unstack, and then f is executed. The steps are the same as in example 1

fContext = {
    scopeChain: [activationObject, checkscopeContext.activationObject, globalContext.variableObject],
}
Copy the code

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * F functions can still be found through the scope chain of f functions. The reason why checkScopecontext. AO is not destroyed is because f refers to the value in checkscopecontext. AO, and because JS does not destroy the variables in the parent context when the child context refers to them.

Es5 version

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

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:

  1. Determine the value of this, also known as this Binding

  2. The LexicalEnvironment component is created

  3. The VariableEnvironment component is created

The pseudocode might look like this:

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

ThisBinding is executed context binding, i.e. every execution context has a this, no different from es3’s this, this value is checked at execution time, not at definition

Create a lexical environment

The lexical environment is structured as follows:

GlobalExectionContext = {  // Global execution context
  LexicalEnvironment: {       // lexical context
    EnvironmentRecord: {     // Environment record
      Type"Object".// Global environment
      // The identifier is bound here
      outer: <null>           // A reference to the external environment
  }  
}
 
FunctionExectionContext = { // Function execution context
  LexicalEnvironment: {     // lexical context
    EnvironmentRecord: {    // Environment record
      Type"Declarative".// Function environment
      // The identifier is bound here // a reference to the external environment
      outer<Global or outer function environment reference>}}Copy the code

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

  • The global environment: is a lexical environment without an external environment, whose external environment is referenced asnull. Having a global object (window object) with its associated methods and properties (such as array methods) and any user-defined global variables,thisThe value points to the global object.
  • Function of the environment: Variables defined by the user in the function are stored in the environment record, containingargumentsObject. A reference to an external environment can be a global environment or an external function environment that contains internal functions.

The lexical environment has two components 👇 :

  • Environment logger: The actual location where variable and function declarations are stored.
  • A reference to the external environment: it points to the next object in the scope chain and has access to its parent lexical environment (scope), similar to es3’s scope chain

There are also two types of environment loggers 👇 :

  • 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.

🎉 accordingly:

  • Create lexical environment use for global contextObject environment logger ,outerA value ofnull;
  • Used when creating the lexical environment for the function contextDeclarative environment logger ,outerValue is a global object, or a parent lexical environment (scope)
Create variable environment

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

In ES6, the difference between lexical and variable environments is that the former is used to store function declarations and variable (let and const keyword) bindings, while the latter is only used to store variable (var) bindings, so the variable environment implements function-level scope and the lexical environment implements block-level scope on top of function scope.

🚨 global variables declared with let/const are bound to Script objects instead of Window objects and cannot be used as window.xx; Global variables declared using var are bound to the Window object; Local variables declared using var/let/const are bound to Local objects. Note: Script objects, Window objects, Local objects are parallel.

Arrow functions have no context of their own, no arguments, and no variable promotionCopy the code

Use examples to introduce

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

When the multiply function is called, the function execution context is created:

GlobalExectionContext = {

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

Why variables are promoted: During the creation phase, function declarations are stored in the environment and variables are set to undefined (in the case of var) or remain uninitialized (in the case of let and const). So this is why it is possible to access variables defined by var (albeit undefined) before the declaration, but if you access variables defined by let and const before the declaration, you will be prompted with reference errors. This is called variable lifting.

Graphical variable promotion:

var myname = "Geek Time"
function showName(){
  console.log(myname);
  if(0) {var myname = Geek Bang
  }
  console.log(myname);
}
showName()
Copy the code

ShowName = myname; showName = myname; showName = myname; showName = myname So the value of myname is undefined.

Execution phase

At this stage, all of these variables are allocated, and finally the code executes. If the JavaScript engine cannot find the let variable’s value in the actual location declared in the source code, it is assigned undefined

Recovery phase

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

Process to summarize

  1. Create a stageCreate the lexical environment for the global context first: create firstObject environment logger“, and then creates his external environment referenceouterThe value is null
  2. Create a variable environment for the global context: same procedure as above
  3. Make this a global object (in the browser case, window)
  4. The function is called, creating the lexical environment for the function context: firstDeclarative environment logger“, and then creates his external environment referenceouter, the value is null, the value is a global object, or the parent lexical environment
  5. Create variable environment for function context: same procedure as above
  6. To determine this value
  7. Enter the execution phase of the function execution context
  8. After the execution is complete, the collection phase is started

Instance to explain

Lexical environmentouterThe execution context structure is as follows:

Let’s analyze the creation and execution of an execution context using the following example:

function foo(){
  var a = 1
  let b = 2
  {
    let b = 3
    var c = 4
    let d = 5
    console.log(a)
    console.log(b)
  }
  console.log(b) 
  console.log(c)
  console.log(d)
}   
foo()
Copy the code

Step 1: before calling foo function to compile and create the execution context and the compile phase will var statement variables stored to the environment, let statement variables to store the lexical environment, it is important to note at this point in the function body let the internal block scope statement variables will not be deposited to the lexical environment, as shown in the figure below 👇 :

Step 2: Continue to execute the code. When executing inside the code block, the value of A in the variable environment has been set to 1, and the value of B in the lexical environment has been set to 2. At this point, the execution context of the function is shown in the figure below:

As can be seen from the figure, when entering the scoped block of a function, variables declared by let in the scoped block are stored in a separate area of the lexical environment. Variables in this area do not affect variables outside the scoped block. So in the example, the variable B declared in the function body block scope and the variable B declared in the function scope exist independently.

Inside the lexical environment, a small stack structure is actually maintained. The bottom of the stack is the outermost variable of the function. After entering a scope block, the variables inside the scope are pushed to the top of the stack. When the execution of the block-level scope is complete, the information for that scope is popped from the top of the stack, which is the structure of the lexical environment.

Step 3: When the code executes to console.log(a) in the scope block, we need to look up the value of variable A in the lexical and variable environments by: Search down the stack of the lexical environment. If it finds something in a block in the lexical environment, it returns it directly to the JavaScript engine. If it doesn’t find something, it continues in the variable environment.

This completes the variable lookup process, as you can see below:

Step 4: When the function’s inner block scope finishes execution, its internal variables are popped from the top of the lexical environment, and the execution context is as follows:

Step 5: When foo completes, the execution stack pops up the execution context of foo.

So, block-level scope is implemented through the stack structure of the lexical environment, and variable promotion is implemented through the variable environment. By combining the two, the JavaScript engine supports both variable promotion and block-level scope.

The outer reference

Outer is an external reference to the external execution context, which is specified by lexical scope

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

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 you 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, and not foo’s execution context?

This is because during JavaScript execution, its scope chain is determined by lexical scope. Lexical scope means that the scope is determined by the position of the function declaration in the code, and therefore is static scope

Combining the variable environment, lexical environment, and scope chain, let’s look at the following code:

function bar() {
  var myName = "Geek World."
  let test1 = 100
  if (1) {
    let myName = "Chrome"
    console.log(test)
  }
}
function foo() {
  var myName = Geek Bang
  let test = 2
  {
    let test = 3
    bar()
  }
}
var myName = "Geek Time"
let myAge = 10
let test = 1
foo()
Copy the code

For the above code, when executing into the if block inside the bar function, the call stack looks like this:

Explain the process. The first step is to look in the execution context of the bar function, but since the test variable is not defined in the execution context of the bar function, according to the rules of lexical scope, the next step is to look in the outer scope of the bar function, that is, the global scope.

reference

  • JavaScript performs context parsing
  • Understand the execution context and execution stack in JavaScript
  • Detailed diagrams of execution context
  • Talk about execution context