In this article we want to explore what is involved when executing JS code. Clarify the meaning of each concept noun. This article is completed on the basis of reading ECMA documents and standing on the shoulders of articles written by various gods and combining their own understanding. Any inaccuracies are to be pointed out.

Get started

In programming languages, variables in code have a range of effects called scope.

scope

Scope is the area of a program’s source code where variables are defined.

A scope specifies how to find a variable, that is, determine what access the currently executing code has to the variable.

There are two types of scopes: static (lexical) and dynamic

Static and dynamic scopes

The characteristic of static scope is that the scope of a function is determined at the time the function is defined.

The scope of a static representation of an identifier and its meaning are determined at the parsing stage. That is, before the program runs, a variable is bound to a scope determined by its location. This is static scope

In contrast to dynamic scoping, the scope of a function is determined at the time the function is called.

JavaScript uses lexical scoping, also known as static scoping.

For a more in-depth look at the differences between the two scopes, check out the links below

  • Ecma-262-5 Lexical Environment: General Theory (I) — scope

  • JavaScript deep lexical scope and dynamic scope


How is its scope implemented in ES6?

First, in ES6, let’s look at Environment Records, the first concept in the Executable code-and-execution-Contexts section

Environment Record

Definition in ES6

The original ES6: A Environment Records is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code. A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Environment Records.

Context record entries are specific structures used to define the associations between identifiers and specific variables and function values based on the ES code lexical nesting syntax. A lexical Environment typically contains an Environment Record entry and a null outer attribute that points to the external lexical Environment.

sec-environment-records

From the definition, it can be seen that the environment record entry contains variable identifiers and their specific contents (such as the defined variable name, function name). (Identifiers can be understood as variable names defined by var and function, etc.)

Classification of environmental record items

  • Declarative Environment Records (DER): stores declarations of variables (variable, const, let, class, module, import, function) in its scope, equivalent to recording variable names and their corresponding declared values.
var foo = 42;
function bar() {};Copy the code

The corresponding DER form is as follows:

  name        value
----------------------
  foo          42
  bar    <function object>
Copy the code
  • Function Environment Records: Also declarative Environment Records. Holds the definition of the outer function and provides the this binding for function if it is not an arrow function, as well as the super keyword. And contains arguments objects

    ecma262/#sec-function-environment-records

  • Object Environment Records (OER) : There is at least one OER, because the Env Record of the Global environment is OER. Binding bindings for property names and their values in Obejct are saved in OER.

Var obj = {answer: 42, name: 'apple'};Copy the code

Corresponding OER:

    name        value
------------------------
    answer       42
    name         apple
Copy the code
  • Global Environment Records (GLOBAL Environment Records) : represents the code environment wrapped by the outermost script tag. This environment record contains the properties of the JS built-in object, the properties of the global object and all the top-level declarations in script.

    global-environment-records

  • Module Environment Records: Represents variable information in the ES6 Module environment. sec-module-environment-records

[[OuterEnv]] outer pointer: pointer to the nearest external lexical environment containing the current lexical environment. Null if global lexical environment. It is through outer that the different code segments form a chain structure.

The outer pointer refers to the environment entry that is outside the function, and is referenced layer by layer outward, so it forms a chain structure. This is the core of the scope chain.

The role of an environment record entry

Registers all identifiers and their specific values in the corresponding scope.

It can be understood that the lexical environment is like a dictionary, which stores the mapping between identifiers and actual references. The JS engine can look up and call the words in this dictionary when it encounters the function name at the specific execution stage.

We can write a piece of code and walk through building the environment record item ourselves:

var a = 1;
function foo() {
  var a = 2;
  console.log(c);/ / 4
  function bar() {
    let c = 6;
    console.log(a);/ / 2
    console.log(c);//6    console.log(d);//5
  }

  function eat() {
    console.log(JSON.stringify(obj));//undefined
    var obj = {
      a:'haha'
    }
    console.log(JSON.stringify(obj));//a obj
  }

  function drink() {
    console.log(JSON.stringify(obj));//key1 obj
  }
  bar();
  eat();
  drink();
}
let c = 4;
const d = 5;
var obj = {
  key1: 'a'.key2: 'b'
}
foo();
Copy the code

The general structure of the corresponding lexical environment is as follows:

globalEnvironment = {
    a:1.foo:foo#FunctionEnvironment,
    c:4.d:5.obj:obj#ObjectEnvironmentRef,
    outer:null
}

obj#ObjectEnvironment= {
    key1:'a'.key2:'b'.outer:globalEnvironment
}

foo#FunctionEnvironment = {
    a:2.bar:bar#FunctionEnvironment,
    eat:eat#FunctionEnvironment,
    outer:globalEnvironment
}

bar#FunctionEnvironment  = {
    c:6.outer:foo#FunctionEnvironment
}

eat#FunctionEnvironment  = {
    obj: obj#ObjectEnvironment,
    outer:foo#FunctionEnvironment
}

drink#FunctionEnvironment  = {
    outer:foo#FunctionEnvironment
}

eatObj#ObjectEnvironment = {
  d:argument
  a:d
}
Copy the code

When is an environment record entry created

The creation of a lexical environment is generally associated with four types of code:

  • Global code: The global source code file starts executionscriptInitializes an environment record entry.
  • Function declaration: Function block execution creates a new lexical environment.
  • Blockstatement (yield, await, return) : blockstatement execution also creates a new environment record entry.
  • The catch statement in try-catch: the catch execution also creates an environment record entry.

Note: Environment record entries are created when these four types of code are executed

sec-environment-records


According to the description in ES6, the environment record item is created during the code execution, indicating that in the JS code execution process, the environment record item must participate in, so how does the environment record item participate in the EXECUTION of JS code?

When executable code is executed, JS will create the corresponding execution context, that is, through the scheduling of execution context, to ensure the orderly execution of JS code.

Execution Context

What is the execution context

Definition of ES600 billion An execution context is a specification device that is used to track the runtime evaluation of code by an ECMAScript implementation. An execution context is a specific structure for tracking ES6 code execution. sec-execution-contexts

JS code will create an execution context data during the execution phase, which defines the data that variables and functions have access to (i.e. the copy of the environment record item corresponding to the current execution code) and the specific behavior corresponding to the execution code.

The execution context is the data that you can refer to, get the values of variables from a small database and process them according to the code execution flow.

What is in the execution context

  • Basic state
phase describe
Code Evaluation State Code that needs to be executed or paused
Function The object is marked if the function code is executed
Realm Code execution in the Realm environment
Script/Module The execution code corresponding to the Module and script tags

LexicalEnvironment

Variables declared by let and const in ES6 are mounted to the lexical environment.

ES6 definition: Identifies the Environment Record used to resolve identifier references made by code within this execution context. A lexical environment is an environment record item that specifically serves the execution context at execution time. So both lexical and variable contexts are environment Records [sec-execution-contexts]

VariableEnvironment

This is also an environment record project, but holds the value of a variable declared by var in the context.

The relationship between AO&VO and lexical environment

In a lot of JS implementation of learning materials, we often see is notLexical Environmentsbutvariable objectandactive object.

In ES5, variable Object was changed to Lexical Environments.

The reasons for this change are:

  • ES5Lexical EnvironmentsIn thedeclarative environment recordsImmutable bindings can be supported. In order to support the laterconstKeyword features.
  • declarative recordsUsing thelexical addressingTechnology to improve variable lookup efficiency in the scope chain.

Why variable object was changed to lexical environment in ES5?

Execution context classification

  • Global Execution Context: The outermost execution environment. In web browsers, the global execution environment defaults toThe window object, the web page or browser will be destroyed only when closed.The global execution environment is always at the bottom of the stack.

Global execution context pseudocode: ECMA262 /#sec-global-environment-records

GlobalExectionContext = {  // Global execution context
  VariableEnvironment: {    	  // lexical context
    GlobalEnvironmentRecord: {   		// Environment record
      [[ObjectRecord]]: {
                  Type: "ObjectEnvironmentRecord".// Bind global objects (such as window)
                 // Globally var, function, generator, async declared identifiers can be saved to the window
      }, 
      [[GlobalThisValue]]:<Object>,
      [[DeclarativeRecord]]:{
                  Type: "DeclarativeEnvironmentRecord".// Identifiers for var, function, generator, async declarations are stored here}, [[VarNames]] : [string list]//DeclarativeRecord specifies the name of the identifier declared
  }  
      outer: <null>  	   		   // References to the external environment are null}}Copy the code

From the contents of the global execution context, we can see that objects declared by lets or const do not appear in the global environment record. So objects declared by lets and const are not mounted under the global object (window in browsers)

console.log(a);// a is not defined
let a = 1;
console.log(a);/ / 1
console.log(window.a);//undefined
console.log(window.c);/ / 3
var c = 3;
Copy the code
  • Functional Execution ContextWhen the execution stream enters a function, its execution environment is pushed onto the environment stack.
FunctionExectionContext = {/ / function execution context LexicalEnvironment: {/ / FunctionEnvironmentRecord lexical environment: {// EnvironmentRecord [[FunctionObject]]: {Type: "ObjectEnvironmentRecord",.. }, [[ThisValue]]:<Object>, [[HomeObject]]:{Type: "DeclarativeEnvironmentRecord", / / var, the function, the identifier of the generator, async statement save} here, [[VarNames]] : } outer: <null> //DeclarativeRecord = null}}Copy the code

Scope chain and execution context

Env Record (outer) {Env Record (outer) {Env Record (outer) {Env Record (outer)}}}

So we can actually understand that the scope chain is the sum of all the entries that are accessible to the lexical and variable environments in the execution context.

Execution Context Stack (ECS)

What is the execution context stack

The execution context stack is a stack structure that uses lifO (FILO) to schedule execution context.

When JS starts executing code, it initializes the global execution environment and pushes it to the bottom of the stack, so the global execution environment is always at the bottom of the stack.

Then, according to the call order of the code, the execution context of each function will be continuously pushed onto the stack. The execution context at the top of the stack will be set to running excution context to be executed. After execution, this context will be removed from the stack. Then the cycle continues until the stack is cleared, at which point the JS code is finished executing and the program will relinquish the thread.

The details of how the stack works can be found in the bitsrc blog

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

It is highly recommended that you take a look at this blog post, which dynamically shows a sequence of execution stacks in the process of code execution. To understand the execution stack, you can write a piece of code by hand, and then draw the sequence diagram of execution stacks yourself.

understanding-execution-context-and-execution-stack-in-javascript

Conceptual summary diagram


Closure

Closure definition

  • From a theoretical point of view: all functions. Because they both store the data of the upper context at the time of creation. This is true even for simple global variables, since accessing a global variable in a function is equivalent to accessing a free variable, using the outermost scope.

  • As a practical matter, a closure is a function that persists (for example, an inner function that returns from a parent function) even if the context in which it was created has been destroyed, referencing free variables in the code

Let’s implement a simple closure from a practical point of view

function clousreOutter(){
	var a = 1;
    let b = 2;
    b++;
    return function(){
    	var a = 3;
        let c = 4;
       	console.log('a',a);
        console.log('b',b);
        console.log('c',c); }}let inner = clousreOutter();
inner();

// output
// a 3
// b 3
// c 4
Copy the code

The closure phenomenon in this code is that after the execution of the clousreOutter function, its execution context is supposedly destroyed, but the internal function of the clousreOutter function can access the value of the variable defined by the external function after the execution of the external function.

Interprets closures from the execution context

Now that we know how to execute a context, why can a closure function access a variable that is not defined within its own function?

From the previous knowledge, we can now see that both the clousreOutter and inner functions have their own environment variable entries.

As we saw earlier, the execution context of a clousreOutter and the environment record entry are created when it is invoked.

But when is the environment entry for the inner function created?

Let’s look at ES6 closure is contained inside the body for the Function of the Function of evaluation is how to deal with: the SEC – 27 and 28 points of functiondeclarationinstantiation:

HasParameterExpressions should be true when there are closures inside the function, so we can see what the Note at point 28 says:

When there are closures in an expression, a new environment log item needs to be created to prevent code outside the closure from accessing variables in the closure.

So we can actually conclude that when the clousreOutter code executes, the inner function’s environment entry has already been created.

If clousreOutter does not store the result of its execution in a variable, then the inner function’s environment entry is not referenced by anyone and will be collected by the JS garbage collection mechanism at the appropriate time.

The problem is that it is returned and stored in a variable, so even if the clousreOutter is finished, because the outer pointer to the inner environment entry points to the clousreOutter environment entry.

The inner function is returned and called, so the inner function’s environment entry is referenced by the execution context.

So following the outer pointer, the inner function can access the variable information of the clousreOutter.

We can draw a picture to help us understand:

Once we understand how closures work in execution, we can look at what can go wrong with commonly used closures if they are not used properly.

  • Abusing closures can lead to memory leaks

When using a closure, as long as the closure is saved, the environment entry of the outer function will be referenced, and JS garbage collection will not destroy the environment entry of the outer function. This data will be stored in JS, causing a memory leak.

The classic closure problem

for(var i = 0; i <5; i++ ) {
    setTimeout(function(){
      console.log(i);
    },0)}Copy the code

The classic closure interview questions cover more than closures, which I explained in detail in another article. 【ECMA learn JS】 Finally, closure classic interview questions really explain

At execution time This points to

JavaScript digs into this from the ECMAScript specification

reference

The reference area for reading the original text is recommended

  • Translated from Dmitry Soshnikov series of articles
  • Ecma-262-5 Lexical Environment series

understanding-execution-context-and-execution-stack-in-javascript

Other reference

Ecma262 /# SEC -lexical environments what- rear-is-a -declarative-environment-record Ecma-262-5 Lexical environment Dmitry Soshnikov ECMA-262-5 in detail. Chapter 3.2. Lexical Environments: ECMAScript implementation. JavaScript deep closure