Describe in detail the execution process of js and the execution principle of javascript

Hello everyone, the editor is here to answer the following questions for you, explain in detail the execution process of js and the execution principle of javascript. Now let us take a look together!

Foreword

JavaScript The execution process is divided into two stages, the compilation stage and the execution stage. During the compilation phase, the JS engine mainly does three things: lexical analysis, syntax analysis and code generation; after the compilation is completed, the JS engine begins to create an execution context (JavaScript code running environment), and execute the JS code python for statement usage.

Compilation phase

For common compiled languages (for example: Java), the compilation steps are divided into: lexical analysis -> syntax analysis -> semantic checking -> code optimization and bytecode generation

For interpreted languages (for example: JavaScript), the compiler stage can interpret and execute the code through lexical analysis -> syntax analysis -> code generation.

Lexical analysis

The JS engine will treat the code we write as a string and decompose it into tokens. For example, var a = 2, this program will be broken down into: “var, a, =, 2,;” five token. Each lexical unit token cannot be subdivided. You can try this website address to view token: Esprima: Parser

Syntactic analysis

The syntax analysis stage will convert the lexical unit stream (array), which is the token mentioned above, into a tree-structured “Abstract Syntax Tree (AST)”

2 Grammar analysis.png

Code generation

The process of converting AST into executable code is called code generation, because computers can only recognize machine instructions, and a certain method needs to be used to convert the AST of var a = 2; A set of machine instructions used to create a variable of a (including allocating memory) and store the value in a.

Execution phase

Executing a program requires an execution environment. Java requires the Java virtual machine. Similarly, parsing JavaScript also requires an execution environment. We call it “execution context” .

What is execution context

In short, an execution context is an abstraction of the JavaScript code execution environment. Whenever JavaScript is run, it is run in the execution context.

Execution context type

There are three execution contexts for JavaScript:

  • Global execution context – When the JS engine executes global code, it will compile the global code and create an execution context. It will do two things: 1. Create a global window object (in browser environment), 2. Set the value of this to the global object; the global context is valid throughout the page life cycle, and there is only one copy.

  • Function execution context – When a function is called, the code in the function body will be compiled and a function execution context will be created. Generally, after the function execution ends, the created function execution context will be destroyed. .

  • eval execution context – Calling the eval function also creates its own execution context (the eval function is prone to malicious attacks and runs code slower than the corresponding alternative, so Not recommended)

Execution stack

The concept of execution stack is relatively close to us programmers. Learning it allows us to understand the working principles behind the JS engine. It helps us debug the code during development and can also cope with interviews about the execution stack during interviews. question.

The execution stack, known as the “call stack” in other programming languages, is a LIFO (last in, first out) stack data structure that is used to store all execution contexts created while the code is running.

When the JS engine starts executing the first line of JavaScript code, it creates a global execution context and pushes it onto the execution stack. Whenever the engine encounters a function call , which creates a new execution context for the function and pushes it onto the top of the stack.

The engine will execute those functions whose execution context is at the top of the stack. When the function execution ends, the execution context is popped from the stack and control flow reaches the next context on the current stack.

Combine the following code to understand:

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');

stack.png

When the above code is loaded in the browser, the JS engine creates a global execution context and pushes it onto the current execution stack. When encountering first() JS the engine creates a new execution context for the function and pushes it onto the top of the current execution stack.

When calling second() from within the first() function, the JS engine creates a new execution context and pushes it onto the top of the current execution stack. When the second() function completes execution, its execution context is popped from the current stack, and the control flow reaches the next execution context, which is the execution context of the first() function.

When first() completes execution, its execution context is popped from the stack and 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.

How to create an execution context

Now that we have understood how the JS engine manages execution context, how is the execution context created?

The creation of execution context is divided into two phases:

  • creation stage;
  • execution stage;
Creation phase

The execution context creation phase will do three things:

  • Bind this
  • Create a lexical environment
  • CreateVariable Environment

So the execution context is conceptually represented as follows:

ExecutionContext = { // Execution context
  Binding This, // this value binding
  LexicalEnvironment = { ... }, // Lexical environment
  VariableEnvironment = { ... }, // Variable environment
}
Bind this

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

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

  • Calling a function through an object method, this points to the calling object
  • After declaring the function, use the function name to call it normally. this points to the global object. In strict mode, the value of this is undefined
  • Use the new method to call the function, and this points to the newly created object
  • Using call, apply, and bind to call a function will change the value of this to point to the first passed in parameters, for example
function fn () {
  console.log(this)
}

function fn1 () {
  'use strict'
  console.log(this)
}

fn() // Ordinary function call, this points to the window object
fn() // In strict mode, this value 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 value to point to the obj object
Lexical environment

Each lexical environment consists of the following two parts:

  • Environment record: variable object => stores declared variables and functions (let, const, function, function parameters)
  • External environment references: scope chain

The official documentation of ES6 defines the lexical environment as:

Lexical Environments (Lexical Environments) are a type of specification used to define the association of identifiers with specific variables and functions based on the lexical nesting structure of ECMAScript code. A lexical environment consists of an Environment Record and a possibly empty reference to the outer Lexical Environment.

Simply put, the lexical environment is a structure of identifier-variable mapping (where identifier refers to the name of the variable/function, variable strong> is a reference to an actual object [objects containing function and array types] or an underlying data type).

As an example, consider the following code:

var a = 20;
var b = 40;
function foo() {
  console.log('bar');
}

The lexical environment of the above code looks like this:

lexicalEnvironment = {
  a: 20,
  b: 40,
  foo: <ref. to foo function>
}

Environmental Records

The so-called environment record is the place where variable and function declarations are recorded in the lexical environment

There are also two types of environment records:

Declare class environment records. As the name suggests, it stores variable and function declarations. The lexical environment of the function contains a declaration class environment record.

Object environment records. The lexical environment in the global environment contains an object environment record. In addition to variable and function declarations, object environment records also include global objects (the browser’s window object). Therefore, for each new property of the object (which, to the browser, contains all properties and methods provided by the browser to the window object), a new entry is created in this record.

Note: For functions, the environment record also contains an arguments object, which is an array-like object that contains parameter indexes and parameter mappings, as well as a length attribute of the parameters passed into the function. For example, an arguments object looks like this:

function foo(a, b) {
  var c = a + b;
}
foo(2, 3);
//The argument object is similar to the following
Arguments: { 0: 2, 1: 3, length: 2 }

Environment record objects are also called variable objects (VO) during the creation phase and active objects (AO) during the execution phase. It is called a variable object because at this time the object only stores the variable and function declarations in the execution context. After the code starts to execute, the variables will be gradually initialized or modified, and then this object is called an active object.

External environment reference

A reference to an external environment means that the external lexical environment is accessible within the current execution context. That is, if a variable cannot be found in the current lexical environment, the Java engine will try to find it in the upper lexical environment. (The Java engine will use this attribute to form what we often call a scope chain)

The lexical environment is abstracted into pseudocode similar to the following:

GlobalExectionContext = { // Global execution context
  this: <global object> // this value binding
  LexicalEnvironment: { //Global execution context lexical environment
    EnvironmentRecord: { // Environment record
      Type: "Object",
     //Identifier is bound here
    }
    outer: <null> // external reference
  }
}
FunctionExectionContext = { // Function execution context
  this: <depends on how function is called> // this value binding
  LexicalEnvironment: { // Function execution context lexical environment
    EnvironmentRecord: { // Environment record
      Type: "Declarative",
      //Identifier is bound here
    }
    outer: <Global or outer function environment reference> // Reference the global environment
   }
}
Variable environment

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

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

In ES6, one difference between lexical environment and variable environment is that the former is used to store function declarations and variable (let and const) bindings, while the latter is only used to store var variable binding.

Take a look at the 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);

Execution looks like this:

GlobalExectionContext = {

  ThisBinding: <Global Object>,

  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Bind identifier here
      a: < uninitialized >,
      b: < uninitialized >,
      multiply: <func>
    }
    outer: <null>
  },

  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Bind identifier here
      c: undefined,
    }
    outer: <null>
  }
}

FunctionExectionContext = {
  ThisBinding: <Global Object>,
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Bind identifier here
      Arguments: {0: 20, 1: 30, length: 2},
    },
    outer: <GlobalLexicalEnvironment>
  },

VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Bind identifier here
      g: undefined
    },
    outer: <GlobalLexicalEnvironment>
  }
}

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

You may have noticed that variables defined by let and const do not have any value associated with them, but variables defined by var are set to undefined.

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

That’s why you can access variables defined by var before declaration (albeit undefined), but let and before declaration Variables that are const will get a reference error.

This is what we call variable declaration hoisting.

Execution phase

After the execution context is created above, the execution of the JavaScript code begins. During execution, 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 undefined.

Execution stack application

Use the browser to view stack call information

We know that the execution stack is a data structure used to manage execution context call relationships, so how do we use it in actual work.

The answer is that we can use the browser “Developer Tools” source tag, select the JavaScript code and put a breakpoint, then we can view the calling relationship of the function, and switch to view each function variable value

Call stack.png

When we put a breakpoint inside the second function, we can see that the Call Stack call stack on the right displays second and first , (anonymous) calling relationship, second is on the top of the stack (anonymous is equivalent to the global execution context at the bottom of the stack), execute second function we can view the values of the function scope Scope local variables a, b and num , by looking at the call relationship of the call stack we can quickly locate the execution of our code.

If an error occurs during code execution and you don’t know where to break the debugging point, how can you check the call stack where the error occurred? Let me tell you a trick, as shown below

Call stack 2.png

We don’t need to break points. By performing the above two steps, we can automatically set breakpoints where the code executes abnormally. After knowing this trick, you no longer have to worry about code errors.

In addition to viewing the call stack through breakpoints above, you can also use console.trace() to output the current function call relationship. For example, console.trace() is added to the second function in the sample code. , you can see the results output by the console, as shown below:

Call Stack 3.png

Summary

JavaScript execution is divided into two phases, the compilation phase and the execution phase. The compilation phase will go through lexical analysis, syntax analysis, and code generation steps to generate executable code; the JS engine will create an execution context when executing the executable code, including binding this, creating Lexical environment and variable environment; the lexical environment creates external references (scope chain) and record environments (variable objects, let, const, function, arguments). JS engine creation and execution start from single thread after completion Go to the next line and execute the JS code line by line.

Finally, some application tips of the call stack during the development process were shared.

Citation link

JavaScript syntax parsing, AST, V8, JIT

[Translation] Understanding execution context and execution stack in JavaScript

Understand execution context and execution stack in Java