1. Introduction

JavaScript itself is an “interpreted” language. Unlike Java, which compiles code to a binary file and hands it to a virtual machine to execute; JavaScript engines “evaluate” a piece of code before running it in the thread of execution. When you evaluate your code, you have variables, functions, this, and other data ready for the code to run. This process of preparing data is creating an Execution environment, which is usually included in Execution context –EC.

Before introducing the execution context, we need to know:

  • The JavaScript parser is runningjsCode time is a single-threaded execution mechanism, so only one piece of code (function) can be executing at any one time;
  • Before running a piece of JavaScript code, the JS parser creates an execution environment (execution context). It does some preparatory work before you run the code so that the information you need (variables, functions, this, and so on) is ready for execution.

Executable Code

When a JS application starts, it executes a piece of “executable code”; When the user triggers an interaction, the application simultaneously fires a specific event, which also executes a piece of “executable code.” So to understand the execution context, you first need to know the “Executable Code.”

There are three types of ECMAScript executable code:

  • Global code is the source code text written in a JavaScript file or embedded in HTML that can be processed by an ECMA script, excluding the source text in the function body.
<script>
    var str = 'guojing';
    function fn(){
        // This is not global code, because it is in the function body
        console.log(str);
    }
    fn()
</script>
Copy the code
  • The Eval code is the source code text passed to the parameter part of the Eval function, which becomes executable code.
eval('alert("hello, world.")'); // The argument is the Eval code
Copy the code
  • Function code is the source code text parsed as the FunctionBody of a function, excluding nested FunctionBody code.
function test(){
    / / here. The code inside the function body is the function code
    var a = 10;
    console.log(a);
}
Copy the code

3. Environmental records

Context records are a canonical type used to define associations of specific variables and function identifiers based on lexical nesting structures in ECMAScript code.

Typically, the lexical context is associated with specific syntactic constructs of ECMAScript code such as FunctionDeclaration, Catch blocks WithStatement, or TryStatement, And a new environment record is created each time similar code is executed.

Each environment record has a [[OuterEnv]] field, which is either null or a reference to an external environment record. This is used to model logical nesting of environment record values. An external reference to an (internal) environment record is a reference to an environment record that logically surrounds an internal environment record. Of course, the external environment record can have its own external environment record. An environment record can serve as an external environment for multiple internal environment records. For example, if a function declaration contains two nested function declarations then the environment record of each nested function will have the environment record of the current evaluation surrounding function as its external environment record.

3.1 Environment Record Types

The environment record can be thought of as a simple object-oriented hierarchy, where the environment record is an abstract class with three concrete subclasses: declarative environment record, object environment record, and global environment record. Function environment record and module environment record are subclasses of declarative environment record.

Environmental records are divided into:

  • Declarative Environment Records

    • Function Environment Records
    • Module Environment Records
  • Object Environment Records

  • Global Environment Records

3.2 Declarative environment record

Each declarative environment record is associated with an ECMAScript program scope that contains variables, constants, lets, classes, modules, imports, and function declarations. A declarative environment record binds a set of identifiers defined by the declarations contained within its scope.

In simple terms, a declarative environment records the binding relationship between an identifier defined for a variable declaration (var, const, let), class, module, import, or function declaration, and so on, and its value.

The function environment records function objects that correspond to ECMAScript calls and contains all bindings declared inside the function. It is also possible to bind this, as well as the state required to support super calls.

The module environment record contains all of its declared bindings at the top, as well as the bindings of other modules that are explicitly imported. Its external environment reference [[OuterEnv]] is a global environment record.

3.3 Object Environment Record

Each object environment record is associated with an object called its binding object. The object environment record binds a set of string identifier names that correspond directly to the property names of its bound object. Property keys that are not strings are not included in the binding identifier set. Regardless of the setting of the [[Enumerable]] property, both your own and inherited properties are included in the collection.

The object environment record is typically used in a WithStatement to directly establish a one-to-one relationship between a series of identifiers and the property names of the object to which they are bound.

The field names value meaning
[[BindingObject]] Object Binding objects
[[IsWithEnvironment]] Boolean Which means whether or notEnvironmental recordsIs forwithStatement created.
var withObj = {name: 'haha'}
with(withObj){
  console.log(name); // 'haha'
  name = 'heihei';
  console.log(name); 
  console.log(constructor);
}

// Object environment record description
// ObjectRecord => {[[BindingObject]]: withObj, [[IsWithEnvironment]]: true}
Copy the code

3.4 Function Environment Record

Function environment records are declarative environment records that represent the top-level scope of a function, providing the This binding if the function is not an ArrowFunction. If a function is not an ArrowFunction function and refers to super, its function environment record also contains the state used to perform the super method call from within the function.

The field names value meaning
[[ThisValue]] Any The value of this when the function is called
[[ThisBindingStatus]] lexical or initialized or uninitialized If the value is lexical, then this is an arrow function with no local this value
[[FunctionObject]] Object The function object that creates a record of the current function environment
[[NewTarget]] Object or undefined If the environment record was created by the [[Construct]] internal method, [[NewTarget]] is the value of the [[Construct]] NewTarget parameter. Otherwise, its value is undefined.

3.5 Global Environment Records

The global environment record is used to represent the outermost scope scope shared by all ECMAScript script elements. Global environment records provide bindings for built-in global variables, properties of global objects, and all top-level declarations that occur in scripts.

The global environment record is logically a single record, but it is specified as a combination of encapsulated object environment record and declarative environment record. Object environment records have global objects as their base objects. The object of the global record the environment record component contains bindings for all built-in global variables as well as those contained in the global code FunctionDeclaration, GeneratorDeclaration, AsyncFunctionDeclaration, AsyncGeneratorDeclaration or VariableStatement introduced all of the binding. Bindings for all other ECMAScript declarations in the global code are contained in the declarative environment record component of the global environment record.

In summary, the global environment record is used to declare bindings for global code, which are created before the code of any ECMA script is executed. A global environment record is specified as a single record of a composite encapsulated object environment record and a declarative environment record. There is no external environment reference, i.e. its [[OuterEnv]] is null.

The field names value meaning
[[ObjectRecord]] Object Environment Record The binding object is a global object. It contains global built-in bindings as well as FunctionDeclaration, GeneratorDeclaration, AsyncFunctionDeclaration and VariableDeclaration bindings in the global code of the associated domain
[[GlobalThisValue]] Object The this value returned in the global scope. The host can provide any ECMAScript object value.
[[DeclarativeRecord]] Declarative environmental records Bindings for all declarations contained in the global code of the correlation domain except FunctionDeclaration, GeneratorDeclaration, AsyncFunctionDeclaration, and VariableDeclaration bindings
[[VarNames]] List of strings FunctionDeclaration, GeneratorDeclaration, AsyncFunctionDeclaration, and VariableDeclaration declare binding string names in the global code of the associated domain.

So FunctionDeclaration, GeneratorDeclaration, AsyncFunctionDeclaration and VariableDeclaration are not in Declarative Environment Record, but in Object Environment Record, This also explains why variables declared with var and function in global code automatically become properties of global objects, while variables declared with let, const, class, etc., do not become properties of global objects.

var x = 1; // VariableDeclaration
function f(){} // FunctionDeclaration
console.log(y) // Uncaught ReferenceError: Cannot access 'y' before initialization
let y = 10;
const z = 'z';
Create environment records before code execution
GER = {
    ObjectRecord: {BindingObject: {... window,x:undefined.f:'<func>' }, IsWithEnvironment: false,},GlobalThisValue:window.DeclarativeRecord: {
        y:<uninitialized> // the code is initialized to undefined only when it specifies the declaration statement "let y"
        z:<uninitialized>
    },
    VarNames: ['x'.'f']}Copy the code

It also helps us understand what variable and function declaration boosts and let/const temporary dead zones are.

var x = 1; // VariableDeclaration
function f() {} // FunctionDeclaration
// console.log(y);
let y;
console.log(y); // undefined
y = 10;
const z = 'z';
Copy the code

2.6 Module Environment Records

The module environment record is a declarative environment record that represents the external scope of an ECMAScript module. In addition to the normal mutable and immutable bindings, module environment records provide immutable import bindings that provide indirect access to target bindings that exist in another environment record.

2.7 Object Structure Description Environment records

// Global environment record
GlobalEnvironmentRecord = {
    ObjectRecord: {BindingObject: window.IsWithEnvironment: false,},GlobalThisValue:window.DeclarativeRecord: {},VarNames: [].OuterEnv: null
} 
// Function environment record
FunctionEnvironmentRecord = {
    ThisValue: <any>,
    ThisBindingStatus: lexical | uninitialized | initialized,
    FunctionObject: <func>
}
Copy the code

2.8 Common Operations for Environment Records

Common operations in the ECMAScript specification are as follows:

2.8.1 GetIdentifierReference ( env.name.strict )

Gets a reference to an identifier.

Parameter: env An environment record or null; Name Indicates the identifier name. Strict bool Indicates whether the mode is strict.

It performs the following steps when invoked:

  1. If env is null, return object {[[Base]]: unresolvable, [[ReferencedName]]: name, [[Strict]]: Strict, [[ThisValue]]: empty}

  2. Define the variable exists with the value? env.HasBinding(name).

  3. If exists is true, the object {[[Base]]: env, [[ReferencedName]]: name, [[Strict]]: Strict, [[ThisValue]]: empty} is returned.

  4. Otherwise,

    A. Define variable outer with the value env.[[OuterEnv]].

    B. to return? GetIdentifierReference(outer, name, strict).

2.8.2 NewDeclarativeEnvironment ( E )

Function: Instantiates a declarative environment record

Parameter: E An environment record

It performs the following steps when invoked:

  1. Define the variable env as a new declarative environment record without any bindings

  2. Set env.[[OuterEnv]] to E.

  3. Returns the env.

2.8.3 NewObjectEnvironment ( O.E )

Function: Instantiates an object environment record

Argument: O an object; E An environmental record

It performs the following steps when invoked:

  1. Define the variable env as a new object environment record and its binding object as O

  2. Set env.[[OuterEnv]] to E.

  3. Returns the env.

2.8.4 NewFunctionEnvironment ( F.newTarget )

Function: Instantiates a function environment record

Argument: F function object; NewTarget Specifies an object

It performs the following steps when invoked:

  1. Assertion: F is an ECMAScript function.

  2. Assertion: Type(newTarget) returns Undefined or Object.

  3. Define the variable env as a new function environment record without any bindings.

  4. Set env.[[FunctionObject]] to F.

  5. If F.[[ThisMode]] is lexical, set env.[[ThisBindingStatus]] to lexical.

  6. Otherwise, set env.[[ThisBindingStatus]] to uninitialized.

  7. Set env.[[NewTarget]] to NewTarget.

  8. Set env.[[OuterEnv]] to F.[[Environment]].

  9. Returns the env.

2.8.5 NewGlobalEnvironment ( G.thisValue )

Function: Instantiates a global environment record

Parameter: G an object; ThisValue this value

It performs the following steps when invoked:

  1. Define the variable objRec value as a new object environment record whose binding object is G.

  2. Define the variable dclRec value as a new declarative environment record without any bindings.

  3. Define the variable env value as a new global environment record.

  4. Set env.[[ObjectRecord]] to objRec.

  5. Set env.[[GlobalThisValue]] to thisValue.

  6. Set env.[[DeclarativeRecord]] to dclRec.

  7. Set env.[[VarNames]] to an empty List.

  8. [[OuterEnv]] to null.

  9. Returns the env.

3. Execute context EC

The execution context is a specification device for tracking runtime evaluations of ECMAScript code.

At any one time, each agent has at most one execution context in which the actual code is executing. This is called the run-time execution context for the agent.

In practice, multiple execution contexts may be created, and one, called the execution context stack, is needed to keep track of the execution context. The running execution context is always the top element of the stack. Every time the JS engine moves from executable code associated with the execution context currently running to executable code independent of that execution context, a new execution context is created. The newly created execution context is pushed and becomes the run execution context.

var a = 10;
function fn(){
  var b = 'b';
  console.log(b);
}
fn();
console.log(a);
Copy the code

First, the global execution context is denoted as GEC, then the fn function’s execution context is denoted as FEC, the execution context stack is denoted as ecStack, the running execution context rec,

  1. Initially ecStack is empty,
  2. Gec is created when global code is executed and unshift to ecStack,ecStack=[gec],rec=ecStack[0].
  3. When entering the FN function call, the feC is created and unshift to ecStack,ecStack=[fec, geC],ec=ecStack[0].
  4. Shift (), feC removed from ecStack,ecStack =[gec],rec=ecStack[0].
  5. Shift (), the gec is removed from the ecStack, and the ecStack is empty again,rec=ecStack[0].

The execution context contains any implementation-specific states needed to track the progress of the execution of its associated code. All execution contexts have components in the following table:

component meaning
Code evaluation status Any state required to execute, pause, and resume the evaluation of the code associated with this execution context.
Function If the execution context is evaluating the code of a function object, the value of the component is that function object. This value is null if the context is evaluating the code for a script or module.
Realm Domain records of associated code accessing ECMAScript resources.
ScriptOrModule The source of the associated code is the Module Record or Script Record. If neither, the value is null.

The value of the Realm component in the running execution context is also called the current Realm Record. Here is a brief introduction to the domain, the main function of the domain is to provide all the built-in objects for us developers. Such as Object, Date, Array, and so on. But not objects provided by the host environment (such as console, location)

The value of the Function component of the running execution context is also called an active Function object.

The execution context of the ECMAScript code has the other state components listed in the following table.

component meaning
LexicalEnvironment Identifies the lexical environment used in this execution context to resolve all identifier references in the code.
VariableEnvironment Identifies the variable environment in this execution context, whose environment record holds the bindings created by VariableStatements.

The LexicalEnvironment and VariableEnvironment components that execute the context are always environment records.

4. How to use it

The concepts related to execution context are covered here, but I’m sure many people still don’t know how these concepts are combined in practice. So here is a brief summary of the general process.

When the JS engine,

  • A new execution environment is created and entered when global code is executed.

  • Each call to a function also creates and enters a new execution environment, even if the function is called recursively on its own.

  • Each call to eval also creates and enters a new execution environment

  • Each return exits an execution environment. Throwing an exception can also exit one or more execution environments.

When entering an execution environment, the this binding, the definition variable environment, and the initial lexical environment are set for that execution environment, and the declarative binding initialization process is performed.

Like this, when the program

  1. When entering the execution environment of the global code,

    1. Initialize the execution context.
    2. Perform declarative binding initialization steps using global code.
  2. Go into the function code

    When entering the execution environment of [function code], based on a function object F, thisArg provided by the caller, and argumentList provided by the caller,

    1. If [function code] is code in strict mode, set this binding to thisArg.
    2. Otherwise if thisArg is null or undefined, set this to be bound as a global object.
    3. Otherwise, if the result of Type(thisArg) is not Object, set this to ToObject(thisArg).
    4. Otherwise set this to bind to thisArg.
    5. In F [[Environment]] internal attribute to invoke NewDeclarativeEnvironment parameters, and order localEnv as the result of a call.
    6. Let the lexical environment component be localEnv.
    7. Let code be the value of the [[code]] internal property of F.
    8. Perform the declarative binding initialization step using the function codes code and argumentList.
  3. Declarative binding initialization

    Each execution context has an associated variable environment component. When an ECMA script is evaluated in an execution environment, the VAR variable definition is added as a binding to the environment record of the variable environment component. For function code, in addition to var definition variables, other parameters are added as bindings to the context record of the lexical context component.

5. Practical application

> < p style = "max-width: 100%; clear: both; ```js var x = 10; function fn(){ console.log(x); } function show(f){ var x = 20; f(); } show(fn); ` ` `Copy the code

First, the above code is considered “global code” because it is not in any function body. Before evaluating and executing, create an “execution context” and set it to run execution context REC and call it the global execution context.

// Just add some related components here
GEC = {
    LexicalEnvironment: {
        [[ObjectRecord]]: {
            [[BindingObject]]: window
        },
        [[GlobalThisValue]]: window,
        [[DeclarativeRecord]]: {},
        [[VarNames]]: [],
        [[OuterEnv]]: null
    },
    VariableEnvironment: {}}Copy the code

By evaluating the code, we know that there are only var and function declarations in the code, and that these declarations are bound to relationships in the object environment record in the environment record. Therefore, it is equivalent to adding an x attribute to the window object, and the default binding value is undefined; [[Eviroment]] is the lexical environment component of the current execution context. Add the FN attribute to the window with the value of this function object. Show.[[Enviroment]] is the lexical environment component of the current execution context. Add the show attribute to the window.

GEC = {
     LexicalEnvironment: {
         [[ObjectRecord]]: {
             [[BindingObject]]: window{x:undefind, fn: '<func>'.show: '<func>'}
         },
         [[GlobalThisValue]]: window,
         [[DeclarativeRecord]]: {},
         [[VarNames]]: [],
         [[OuterEnv]]: null
     },
     VariableEnvironment: {}}Copy the code

When the code executes x = 10, the x binding value of the global execution context is set to 10;

GEC = {
     LexicalEnvironment: {
         [[ObjectRecord]]: {
             [[BindingObject]]: window{x:10.fn: '<func>'.show: '<func>'}
         },
         [[GlobalThisValue]]: window,
         [[DeclarativeRecord]]: {},
         [[VarNames]]: [],
         [[OuterEnv]]: null
     },
     VariableEnvironment: {}}Copy the code

When the show(fn) code is executed, a new execution context (showFEC) is created and set to run execution context, since the function code is to be executed.

// function show execution context
showFEC = {
    LexicalEnvironment: {
        [[ThisValue]]: any,
        [[ThisBindingStatus]]: uninitialized,
        [[FunctionObject]]: show,
        [[OuterEnv]]: GEC.lexical
    },
    VariableEnvironment: {}}Copy the code

By evaluating the code inside the show function body, we found that there is only one var declaration statement and one parameter f, so we only need to add the binding in the lexical environment.

showFEC = {
    LexicalEnvironment: {
        [[ThisValue]]: window.// Non-strict mode
        [[ThisBindingStatus]]: initialized,
        [[FunctionObject]]: show,
        x: undefined.f: fn,
        [[OuterEnv]]: GEC.lexical
    },
    VariableEnvironment: {}}Copy the code

When the show function starts executing the code body, x = 20 is the first, at which point the x binding value in the record is changed to 20.

showFEC = {
    LexicalEnvironment: {
        [[ThisValue]]: window.// Non-strict mode
        [[ThisBindingStatus]]: initialized,
        [[FunctionObject]]: show,
        x: 20,
        [[OuterEnv]]: GEC.lexical
    },
    VariableEnvironment: {}}Copy the code

Then f is called, and the f binding value FN can be obtained from the lexical environment by calling GetIdentifierReference and GetBindingValue. Then, before the fn function is executed, a new execution context is still created and set to run execution context.

Similar to the show function:

fnFEC = {
    LexicalEnvironment: {
        [[ThisValue]]: window.// Non-strict mode
        [[ThisBindingStatus]]: initialized,
        [[FunctionObject]]: fn,
        [[OuterEnv]]: GEC.lexical
    },
    VariableEnvironment: {}}Copy the code

You will see that the external environment records for showFEC and fnFEC are both lexical environments for the global execution context, since both functions are defined in the global execution context.

Ok. Start executing f body code. Console. log(x), to obtain the value of variable x, first look in the current lexical environment, not from the external record of the lexical environment, continue to search until the global execution context lexical environment, if not found, raise reference exception, return the value directly.

So, if it’s not in fnFEC. Lexical, it goes to GEC. Lexical, and gets x with a value of 10. So the final output is 10.

6. Summary

Execution context is the most important and key knowledge in JavaScript. If we get this right, then we can also get scope-specific and closing-related information out of the way.

If you feel that you still don’t understand, you can do more through the interview questions to repeatedly temper. Make all the points in this article flexible and you’ll have a real understanding of how JavaScript works.

Here are some more interview questions you can continue to have fun with.

6.1 An easy one

var a = 10;

function f(a){
    console.log(a)
    var a = 20;
    console.log(a)
}

f(a)
Copy the code

6.2 Advanced variant

function f(a){
    console.log(a)
    var a = 20;
    function a(){}
    console.log(a);
}

f(100)
Copy the code

6.3 ultra pervert

var x = 1;
function f(x,y = function () {x = 3;console.log(x); }) {
    console.log(x);
    var x = 2;
    y();
    console.log(x);
}
f();
console.log(x);
Copy the code

Look at the results, you must not expect…