I believe that many partners will often be confused with the concepts of scope, execution context, lexical environment and so on when they first learn JavaScript. In fact, they do not have a clear understanding of these knowledge points.

In my previous article, I have provided a more detailed and clear description of JavaScript scopes and scope chains: a detailed explanation of JavaScript scopes and scope chains

So this article will not explain the scope too much, and will focus on the main characters of this article: the execution context and lexical environment.

Execution context

First, what is the execution context?

Officially, an Execution Context stack (ECS) is an abstraction of the environment in which JavaScript code is evaluated and executed. In layman’s terms, whenever Javascript code is running, it is running in an execution context.

There are three execution contexts 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 globalwindowObject (browser case), and setthisIs equal to the global object. There is only one global execution context in a program.
  • Function execution context – Each time a function is called, a new execution 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. Each time a new execution context is created, it performs a series of steps in a defined order (discussed later).
  • The Eval function executes the context– perform inevalThe code inside a function also has its own execution context, but it is not used very ofteneval, so it will not be discussed here.

The life cycle of execution context consists of three stages: create stage → execute stage → reclaim stage. This article focuses on the create stage.

(1) Creation stage

Before the JavaScript code executes, the execution context goes through the creation phase. Three things happen during the creation phase:

  1. The determination of the this value, known as the This binding.
  2. createLexical environmentComponents. (LexicalEnvironment component)
  3. createThe variable environmentComponents. (VariableEnvironment component)

(Lexical and variable environments are explained below)

So the execution context can be expressed in pseudocode like this:

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

This binding:

In the context of global execution, the value of this points to a 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. If it is called by a reference object, this is set to that object, otherwise this is set to global or undefined (in strict mode).

(2) Execution stage

Perform variable assignment and code execution.

(3) Recovery stage

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

Note: At execution time, if the JavaScript engine cannot find the value of the let variable at the actual location declared in the source code, it will be assigned the value of undefined.

Execution context stack

The Execution Context Stack (also called the call Stack) is used to describe the Execution Context Stack. It is a stack with LIFO (Last in, first out) data structures that are used to store all execution contexts created while the code is running.

When the JavaScript engine first encounters our script, it creates a global execution context and pushes it onto the current call stack. Each time the engine encounters a function call, it creates a new function 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:

let 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');Copy the code

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.

Lexical environment

Official definition: A lexical environment is a canonical type that defines the associations of identifiers and concrete variables and functions based on the lexical nesting structure of ECMAScript code. A lexical environment consists of an environment logger and a null value that may reference an external lexical environment.

Simply put, a lexical environment is a structure that holds identity-variable mappings. (The identifier here refers to the name of the variable/function, and the variable is a reference to the actual object [containing the object of the function type] or the original data).

It can also be said that the lexical environment is an embodiment of the association between identifiers and variable values and function values in the corresponding code block.

There are two types of lexical environments:

  • The global environment(in the context of global execution) is a lexical environment that has no references to the external environment. The external environment reference for the global environment isnull. It has built-inObject/ArrayEtc., the prototype function in the environment logger (associating global objects, such aswindowObject) and any user-defined global variables, andthisThe value points to the global object.
  • In a function environment, user-defined variables within a function are stored in the environment logger. And the referenced external environment may be the global environment, or any external function that contains this internal function.

Inside the lexical environment there are two components:

  • Environment logger: Is the actual location where variable and function declarations are stored.
  • Reference to an external environment: means it has access to its parent lexical environment.

There are also two types of internal environment loggers, based on the two types of lexical environment:

  1. Declarative environment logger (in a functional environment) : Stores variables, functions, and parameters.
  2. Object environment logger (in the global context) : Used to define the relationship between variables and functions that appear in the global context.

Note: For functional environments, the declarative environment logger also contains a arguments object passed to the function (which stores the index and parameter mapping) and the length of the argument passed to the function.

Abstractly, the lexical environment in pseudocode looks like this:

GlobalExectionContext = {// global execution context LexicalEnvironment: {// lexical EnvironmentRecord: {// environment logger: the actual location where variables and function declarations are stored Type: "Object", // bind identifier here} outer: <null> // references to external environments: parent LexicalEnvironment can be accessed}} FunctionExectionContext = {// function execution context LexicalEnvironment: {EnvironmentRecord: } outer: <Global or outer function environment reference>}}Copy the code

The variable environment

It is also a lexical environment whose environment logger holds bindings created in the execution context of variable declaration statements.

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

The concept of a separate variable environment in the ES5 specification is intended to serve ES6: One difference between the lexical environment component and the variable environment in ES6 is that the former is used to store function declarations and variable (let and const) bindings, while the latter is used only to store var variable bindings.

Let’s look at sample code to understand the above concepts:

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 looks like this:

GlobalExectionContext = {ThisBinding: <Global Object>, LexicalEnvironment: {// lexical EnvironmentRecord: {Type: < initialized >, // let, const const < uninitialized >, // let, const const multiply: < func > // function declaration} outer: <null>}, VariableEnvironment: {// EnvironmentRecord: {Type: "Object", // } outer: <null>}} FunctionExectionContext = {ThisBinding: <Global Object>, LexicalEnvironment: {// lexical EnvironmentRecord: {Type: "Declarative", // }, outer: <GlobalLexicalEnvironment>}, VariableEnvironment: {// variable EnvironmentRecord: {Type: "Declarative", // g: undefined // var declaration variable}, outer: <GlobalLexicalEnvironment> } }Copy the code

Note – The function execution context is created only if the multiply function is called.

You may have noticed that the let and const variables are not associated with any value, but the var variable is set to undefined.

This is because during the creation phase, the engine examines the code for variable and function declarations, which are stored entirely in the environment but are initially set to undefined (in the case of var) or uninitialized (in the case of let and const).

This is why you can access variables defined by var (albeit undefined) before declaration, but access let and const variables before declaration will get a reference error.

This is what we call variable declaration enhancement.

Analyze the whole process of program execution

  • The program starts, the global execution context is created and pushed onto the call stack

    1. Create a lexical environment for the global context

      1. createObject environment loggerIt is used to define the appearance inGlobal contextThe relationship between variables and functions inlet 和 constDefined variables)
      2. Creates an external environment reference with a value of null
    2. Create a variable environment for the global context

      1. createObject environment loggerIt heldVariable declaration statementBindings created in the execution context (responsible for handlingvarDefines a variable with an initial value ofundefinedCause the statement to improve)
      2. Creates an external environment reference with a value of null
    3. Make this a global object (in the browser case, window)

  • The function is called, the function execution context is created and pushed onto the call stack

    1. Create the lexical environment for the function context

      1. createDeclarative environment logger, stores variables, functions, and arguments, and contains aargumentsObject, which stores the index and parameter mapping, and the parameters passed to the functionlength. (In charge of handlinglet 和 constDefined variables)
      2. Create an external environment reference with a value of a global object, or a parent lexical environment (scope)
    2. Create the variable environment for the function context

      1. createDeclarative environment logger, stores variables, functions, and arguments, and contains aargumentsObject, which stores the index and parameter mapping, and the parameters passed to the functionlength. (In charge of handlingvarDefines a variable with an initial value ofundefinedCause the statement to improve)
      2. Create an external environment reference with a value of a global object, or a parent lexical environment (scope)
    3. To determine this value

  • Enter the execution phase of the function execution context:

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

conclusion

Now let’s sum it up:

First of all, JavaScript is an interpreted language. JavaScript execution is divided into two phases: interpret and execute, and the two phases do different things:

Interpretation stage:

  • Lexical analysis
  • Syntax analysis
  • Scope rules are determined

Execution stage:

  • Create execution context
  • Execute function code
  • The garbage collection

The JavaScript interpretation phase determines the scope rule, so the scope is defined when the function is defined, not when the function is called, but the execution context is created before the function executes. The most obvious aspect of the execution context is that the reference to this is determined at execution time. The variables accessed by scope are determined by the structure of the code that was 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 and does not change.

A scope may contain several contexts. It is possible that there is never a context (the function is never called); It may have, but now the context is destroyed after the function is called; It is possible to have one or more at the same time. In the same scope, different calls will have different execution contexts, resulting in different variable values.

Finally, a brief overview of the concepts of scope, lexical context, and execution context:

  • Scope: A scope is a separate area that keeps variables from being exposed. Inner scopes can access outer scopes. The greatest use of a scope is to isolate variables. Variables of the same name in different scopes do not conflict.
  • Lexical environment: an embodiment of the association between identifiers and variable values and function values in the corresponding code block. The word environment contains the environment logger and references to the external environment.
  • Execution context: The context in which the JavaScript code runs. There are global execution contexts, function execution contexts, and eval function execution contexts (the first two are more common).

reference

Understanding execution context and stack in JavaScript Interviewer: Talk about execution context

The articles

Do you really understand script tags? Use mock. js to simulate back-end interface data.