6

What is V8?

V8 is an open source JavaScript engine developed by Google. It is currently used in Chrome browser and Node.js. Its core function is to execute JavaScript code that is easy for humans to understand.

V8 uses a technology that mixes compilers and interpreters, called JIT (Just In Time) technology.

Below is the flow chart of V8 executing JavaScript code:

image-20210520090932069

First understand the related concepts

Stack space (Stack)

The stack space here is the Call Stack, which is used for store the execution context .

In the process of function call, the content related to the context will be stored on the stack, such as the original type, the address of the referenced object, the execution status of the function, the value of this, etc. will all be stored on the stack. Be a
The function execution ends, then the execution context of the function will be destroyed.

Why use a stack structure to manage function calls?

We know that most high-level languages ​​use the stack structure to manage function calls. Why? This is related to the characteristics of the function. Usually functions have two main characteristics:

  1. The first feature is that functions can be called. You can call another function in one function. When the function call occurs, the control of the execution code will be transferred from the parent function to the child function. After the child function is executed, it will Control of code execution is returned to the parent function;
  2. The second feature is that the function has a scope mechanism. The so-called scope mechanism means that the variables defined in the function can be isolated from the external environment when the function is executed. The variables defined in the function are also called temporary variables. Variables can only be accessed in this function. External functions usually have no right to access. When the function is executed, the temporary variables stored in memory are also destroyed.

We can first look at the following piece of C code:

int getZ() {
    return 4;
}
int add(int x, int y) {
    int z = getZ();
    return x + y + z;
}
int main() {
    int x = 5;
    int y = 6;
    int ret = add(x, y);
}

The specific function call diagram is as follows:

image-20210520131342132

We can conclude that is always longer than the callee (last in), and the life cycle of the callee is always before the end of the caller's life cycle (first out) .

Because the function has a scope mechanism, the scope mechanism is usually manifested in that when the function is executed, the internal variables and context of the function will be allocated in the memory. After the function is executed, the internal data will be destroyed.

So from the perspective of function resource allocation and recovery, the resource allocation of the called function is always later than the calling function (last in), and the release of function resources is always before the calling function (first out). is shown in the figure below:

image-20210520131508506

By observing the life cycle of the function and the resource allocation of the function, we found that they all comply with the last-in first-out (LIFO) strategy, and the stack structure just meets this last-in-first-out (LIFO) demand, so we choose the stack It is a natural choice to manage the function call relationship.

How does the stack manage function calls?

When a function is executed, the parameters of the function and the variables defined in the function will be pushed onto the stack in turn. We will analyze this process in combination with the actual code. You can refer to the following figure:

image-20210520132107789

  • When the first piece of code of the function is executed, the variable x is assigned a value for the first time, and the value is 5, then 5 will be pushed onto the stack.
  • Then, execute the second piece of code, the variable y is assigned a value for the first time, and the value is 6, then 6 will be pushed onto the stack.
  • Then, execute the third piece of code. Note that the variable x is assigned for the second time, and the new value is 100. At this time, instead of pushing 100 onto the stack, it replaces the content previously pushed onto the stack. That is, replace the 5 in the stack with 100.
  • Finally, execute the fourth piece of code, this code is int z = x + y, we will first calculate the value of x + y, and then assign the value of x + y to z, because z is assigned for the first time , So the value of z will also be pushed onto the stack.

You will find that function, its internal temporary variables will be pushed onto the stack in the order of execution.

Let's take a look at a more complicated scenario:

int add(num1, num2) {
    int x = num1;
    int y = num2;
    int ret = x + y;
    return ret;
}
int main() {
    int x = 5;
    int y = 6;
    x = 100;
    int z = add(x, y);
    return z;
}

We transformed x+y in the previous code into an add function. When int z = add(x,y) is executed, the current stack status is as follows:

image-20210520132829754

Next, the add function must be called. Ideally, the process of executing the add function is as follows:

image-20210520132850265

When the add function is executed, the parameters num1 and num2 will be pushed onto the stack first, and then we will push the values ​​of the variables x, y, and ret onto the stack in turn. However, when executing this, we will encounter a problem, that is, when the add function is executed. After that, the control of the execution code needs to be transferred to the main function, which means that the state of the stack needs to be restored to the state when the main function was executed last time. We call this process restore the scene .

So how to restore the execution scene of the main function?

In fact, the method is very simple. As long as you save a pointer that always points to the top of the current stack in the register, the function of the top pointer is to tell you where to add a new element. This pointer is usually stored in the esp register. If you want to add an element to the stack, you need to find the position of the current stack top according to the esp register, and then add a new element above the top of the stack. After the new element is added, you also need to update the address of the new element to the esp register .

With the stack top pointer, it is easy to restore the execution scene of the main function. When the add function is executed, you only need to move the stack top pointer down. For details, you can refer to the following figure:

state is about to add a function to perform end

recovery mian function execution site

Observing the above picture, you can move the pointer of esp down to the place where the main function was executed before, but a new problem is coming again. How does the CPU know to move to this address?

The solution of the CPU is to add another ebp register to save the starting position of the current function. We also call the starting position of a function stack frame pointer . The ebp register saves the stack frame of the current function. Pointer, as shown in the figure below:

image-20210520134342721

When the main function calls the add function, the stack top pointer of the main function becomes the stack frame pointer of the add function, so the stack top pointer of the main function needs to be saved in ebp. When the add function is finished, I need to destroy it. add the stack frame of the function and restore the stack frame of the main function, you only need to take out the stack top pointer of the main function and write it to esp (the stack top pointer of the main function is stored in ebp), which is equivalent to The pointer on the top of the stack moves to the area of ​​the main function.

So now, can we execute the main function?

The answer is still "no". This is mainly because the main function also has its own stack frame pointer. Before executing the main function, we need to restore its stack frame pointer. How to restore the stack frame pointer of the main function?

The usual method is to call the add function in the main function, the CPU will save the stack frame pointer of the current main function in the stack, as shown in the following figure:

image-20210520134442690

After the function call is over, it is necessary to restore the execution scene of the main function. First, take out the pointer in ebp and write it into esp, then take out the previously reserved stack frame address of main from the stack and write it into ebp. Here both ebp and esp are restored, and the main function can continue to be executed.

In addition, here, we also need to add stack frame , because we will see this concept in many articles, each stack frame corresponds to an unfinished function, and the return of the function is saved in the stack frame Address and local variables.

Above we have analyzed the execution process of the C function in detail. In JavaScript, the execution process of the function is similar. If a new function is called, V8 will create a stack frame for the function, and after the function execution ends, the stack frame will be destroyed. The capacity of the stack structure is fixed, so if a function is repeatedly nested, it will cause the stack to overflow.

Heap space (Heap)

Okay, we now understand how the stack manages function calls. There are many advantages to using the stack:

  1. The structure of the stack is very suitable for the function call process.
  2. The speed of allocating and destroying resources on the stack is very fast. This is mainly due to the continuous stack space. Allocating and destroying space only need to move the pointer.

Although the operation speed is very fast, the stack also has shortcomings. The biggest shortcoming is also caused by its advantages, that is, the stack is continuous, so it is very difficult to allocate a large continuous space in the memory. The stack space is limited.

Because the stack space is limited, this leads us to often accidentally cause stack overflow when we write programs. For example, too many function loop nesting levels or too large data allocated on the stack will cause stack overflow. , It is not convenient to store large data based on the stack, so we use another data structure to store some large data, which is heap .

Heap space is a tree-shaped storage structure used to store discrete data of object types. In JavaScript, except for primitive types of data, all other types of objects are object types, such as functions and arrays. In the browser, there are also window objects, Document objects, etc., these are all in the heap space.

Unlike the stack space, the data stored in the heap space is not required to be stored continuously. There is no fixed pattern for allocating memory blocks from the heap. You can allocate and release it at any time. In order to better understand the heap, let’s look at the following How is this code executed:

struct Point
{
    int x;
    int y;
};
int main()
{
    int x = 5;
    int y = 6;
    int *z = new int;
    *z = 20;

    Point p;
    p.x = 100;
    p.y = 200;

    Point *pp = new Point();
    pp->y = 400;
    pp->x = 500;
    delete z;
    delete pp;
    return 0;
}

Observing the above code, you can see that there are statements such as new int and new Point in the code. When these statements are executed, it means that a piece of data is allocated in the heap and then the pointer is returned. Usually the returned pointer will be saved to the stack. In, let's take a look at the state of the heap and stack when the main function is almost finished. For details, you can refer to the following figure:

image-20210520134919118

Observing the above picture, we can find that when using new, we will allocate a space in the heap. After allocating space in the heap, the allocated address will be returned, and we will save the address in the stack, as shown in the above figure. And pp are addresses, they are stored in the stack, pointing to the space allocated in the heap.

Usually, when the data in the heap is no longer needed, it needs to be destroyed. You can use free in C language and delete in C++ language.

JavaScript and Java use an automatic garbage collection strategy to achieve automatic garbage collection, but there are always two sides to it. Automatic garbage collection will also bring us some performance problems.

Execution Context

In short, the execution context is the abstract concept of the environment where the current JavaScript code is parsed and executed. Any code running in JavaScript runs in the execution context.

There are three types of execution contexts:

  • When JavaScript executes the global code, it will compile the global code and create the global execution context , and there is only one copy of the global execution context in the life cycle of the entire page.
  • When a function is called, the code in the function body will be compiled and the function execution context will be created. Generally, after the function execution ends, the created function execution context will be destroyed.
  • When using the eval function, eval code will be compiled, and create execution context .

Creation phase

Before any JavaScript code is executed, the execution context is in the creation phase. A total of two things happened during the creation phase:

  1. LexicalEnvironment component is created.
  2. VariableEnvironment component is created.

Therefore, the execution context can be conceptually expressed as follows:

ExecutionContext = {  
  LexicalEnvironment = { ... },  
  VariableEnvironment = { ... }, 
}

Lexical Environment

official ES6 document defines the lexical environment as:

Lexical environment is a type of specification, based on the lexical nesting structure of ECMAScript code to define the relationship between identifiers and specific variables and functions. The lexical environment consists of an environment record (environment record), an external lexical environment that may be a null reference (null), and this binding.

In short, the lexical environment is a structure that contains the identifier variable mapping . (The identifier here represents the name of the variable/function, and the variable is a reference to the actual object [including the function type object] or the original value).

E.g:

var a = 20;
var b = 40;

function foo() {
    console.log('bar');
}

The lexical environment above looks like this:

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

In the lexical environment, there are three components:

  1. environment record (environment record) ;
  2. to Outer Environment ;
  3. This binding

Environmental Record

Is the actual location where variable and function declarations are stored.

There are also two types of environmental records

  • declarative environment record stores variable and function declarations. function code contains a declarative environment record.
  • object environment record global code contains an object environment record. In addition to variable and function declarations, the object environment record also stores a global binding object (a window object in the browser). Therefore, for each binding object property (in the browser, it contains the properties and methods provided by the browser window object), a new entry is created in the record.
For the function code, the environmental index record contains the object and transmitted to the mapping between the parameters and the parameters passed to the function of the function length (number) . For example, the arguments object of the following function looks like this:
function foo(a, b) {
  var c = a + b;
}
foo(2, 3);
// argument object
Arguments: {0: 2, 1: 3, length: 2},

to the external environment

The reference to the external environment means that it can access its parent lexical environment (scope). This means that if the variable cannot be found in the current lexical environment, the JavaScript engine will look for it in the parent lexical scope.

This Binding

In the global execution context, this points to the global object (in the browser, this points to the window object).

In the context of function execution, this depends on how the function is called. If it is called by an object reference, then this is set to the object, otherwise this is set to the global object or undefined (in strict mode). E.g:

const person = {
  name: 'peter',
  birthYear: 1994,
  calcAge: function() {
    console.log(2018 - this.birthYear);
  }
}
person.calcAge(); 
// 'this' refers to 'person', because 'calcAge' was called with //'person' object reference
const calculateAge = person.calcAge;
calculateAge();
// 'this' refers to the global window object, because no object reference was given

In abstract terms, the lexical environment looks like this pseudo code:

GlobalExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
    }
    outer: <null>,
    this: <global object>
  }
}
FunctionExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
    }
    outer: <Global or outer function environment reference>,
    this: <depends on how function is called>
  }
}

For details, please refer to the previous [[JavaScript summary]Comprehensive analysis of this binding]( https://segmentfault.com/a/1190000039907394)

Variable Environment

It is also a lexical environment, and its EnvironmentRecord contains the VariableStatements in this execution context.

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

In ES6, LexicalEnvironment component and the VariableEnvironment component is that the former is used to store function declarations and variables ( let and const ) binding, while the latter is only used to store variables ( var ) cf49bfd.

In ES2018, the execution context has become like this again, this value is classified into the lexical environment, but a lot of content has been added.

  • lexical environment: Lexical environment, used when getting a variable or this value.
  • variable environment: variable environment, used when declaring variables.
  • code evaluation state: used to restore the code execution position.
  • Function: Used when the task to be executed is a function, indicating the function being executed.
  • ScriptOrModule: Used when the task to be executed is a script or a module, indicating the code being executed.
  • Realm: The basic library and built-in object instances used.
  • Generator: Only the generator context has this attribute, which means the current generator.

Scope

Scope refers to the area where variables are defined in the program, and this location determines the life cycle of the variable. In layman's terms, scope is the accessible scope of variables and functions, that is, scope controls the visibility and life cycle of variables and functions .

There are three scopes of ECMAScript:

  • Objects in the global scope can be accessed anywhere in the code, and their life cycle is accompanied by the life cycle of the page.
  • Function scope is the variable or function defined inside the function, and the defined variable or function can only be accessed inside the function. After the function execution ends, the variables defined inside the function will be destroyed.
  • Block-level scope can be let and const , and the declared variable can not be accessed outside the specified block-level scope.

Variable promotion (Hoisting)

so-called variable promotion refers to the "behavior" that the JavaScript engine promotes the declaration part of the variable and the declaration part of the function to the beginning of the code during the execution of the JavaScript code. After the variable is promoted, a default value will be set for the variable. This default value is the familiar undefined .

Take a look at the following code:

showName()
console.log(myname)
var myname = 'JavaScript'
function showName() {
    console.log('函数showName被执行');
}

Analyze the above code:

  • Lines 1 and 2, because these two lines of code are not declarative operations, the JavaScript engine will not do any processing;
  • In line 3, since this line is declared by var, the JavaScript engine will create a property named myname in the environment object and initialize it with undefined;
  • In line 4, the JavaScript engine found a function defined by function, so it stores the function definition in the heap (HEAP), creates a showName attribute in the environment object, and then points the attribute value to the location of the function in the heap . In this way, a variable environment object is generated.

After compilation, two parts are generated: Execution context and executable code.

//执行上下文的变量环境保存了变量提升的内容,也就是myname变量,词法环境保存了showName()。
var myname = undefined
function showName() {
    console.log('函数showName被执行');
}
//可执行代码
showName()
console.log(myname) // undefined
myname = 'JavaScript'

The JavaScript engine begins to execute the "executable code", which is executed line by line in order.

  • When the showName function is executed, the JavaScript engine starts to look for the function in the variable environment object. Because the function reference exists in the variable environment object, the JavaScript engine starts to execute the function and outputs the result of "function showName is executed";
  • Next, print the "myname" information, and the JavaScript engine will continue to look for the object in the variable environment object. Since the myname variable exists in the variable environment and its value is undefined, undefined will be output at this time;
  • Next execute line 3, assign JavaScript to the myname variable, and after the assignment, the value of the myname attribute in the variable environment is changed to JavaScript.

Problems caused by variable promotion

1. Variables are easily overwritten without being noticed

var myname = "JavaScript"
function showName(){
  console.log(myname);
  if(0){
   var myname = "CSS"
  }
  console.log(myname);
}
showName() //undefined

2. Variables that should be destroyed are not destroyed

function foo(){
  for (var i = 0; i < 7; i++) {
  }
  console.log(i); 
}
foo() //7,因为变量提升,for循环结束的时候 i 没有被销毁

So in order to solve this problem, the block-level scope .

Scope chain

In fact, in the lexical (variable) environment of each execution context, an external reference is included to point to the external execution context. We call this external reference outer.

Look at the following code:

function bar() {
    console.log(myName)
}
function foo() {
    var myName = " CSS "
    bar()
}
var myName = " JavaScript "
foo() // JavaScript

It can be seen from the figure that the outer of the bar function and the foo function point to the global context, which means that if an external variable is used in the bar function or the foo function, the JavaScript engine will look for it in the global execution context. We call this lookup chain called the scope chain .

The bar function called by the foo function, then why the external reference of the bar function is the global execution context, not the execution context of the foo function?

This is because according to the lexical scope, the parent scope of foo and bar is the global scope, so if the foo or bar function uses a variable that they have not defined, then they will look in the global scope. In other words, the lexical scope is determined at the code stage, and has nothing to do with how the function is called.

What is lexical scope?

Lexical scope

Lexical scope means that the scope is determined by the position of the function declaration in the code, so the lexical scope is a static scope, through which it is possible to predict how the code will look for identifiers during execution.

It can be seen from the figure that the lexical scope is determined according to the location of the code. The main function contains the bar function, and the bar function contains the foo function. Because the JavaScript scope chain is determined by the lexical scope, the entire The order of the lexical scope chain is: foo function scope—>bar function scope—>main function scope—>global scope.

Closure

Three features in JavaScript:

First, the JavaScript language allows a new function defined inside the function. The code is as follows:

function foo() {
    function inner() {
    }
    inner()
}

The reason why you can declare another function in a function in JavaScript is mainly because a function in JavaScript is an object. You can declare a variable in a function, and of course you can also declare a function in a function.

Second, defined in the parent function in the internal function. The code is as follows:

var d = 20
//inner函数的父函数,词法作用域
function foo() {
    var d = 55
    //foo的内部函数
    function inner() {
      return d+2
    }
    inner()
}

Since new functions can be defined in functions, it is natural that internal functions can use variables defined in external functions.

Third, is a First Class Function, the function can be used as the return value . We can look at the following code:

function foo() {
    return function inner(a, b) {
        const c = a + b 
        return c
    }
}
const f = foo()

Observing the above code, we use the inner function as the return value of the foo function. That is to say, when the foo function is called, the inner function will eventually be returned to the caller. For example, we returned the inner function to the global variable f above. Then you can call f externally like the inner function.

After understanding these three features of JavaScript, take a look at the following closure code:

function foo() {
    var myName = " JavaScript "
    let test1 = 1
    const test2 = 2
    var innerBar = {
        getName:function(){
            console.log(test1)
            return myName
        },
        setName:function(newName){
            myName = newName
        }
    }
    return innerBar
}
var bar = foo()
bar.setName(" CSS ")
bar.getName()
console.log(bar.getName()) //1 1 CSS

First, let's look at the call stack when the line of code return innerBar inside the foo function is executed. You can refer to the following figure:

As can be seen from the above code, innerBar is an object that contains two methods of getName and setName (usually we call the functions inside the object as methods).

As you can see, these two methods are both defined inside the foo function, and both methods use two variables, myName and test1.

According to the rules of lexical scope, the internal functions getName and setName can always access the variables in their external function foo, so when the innerBar object returns to the global variable bar, although the foo function has been executed, the getName and setName functions can still be used Use the variables myName and test1 in the foo function.

So when the foo function is executed, the state of the entire call stack is as shown in the figure below:

As can be seen from the above figure, after the execution of the foo function is completed, its execution context pops up from the top of the stack, but since the returned setName and getName methods use the internal variables myName and test1 of the foo function, these two variables are still stored in In memory. This is like an exclusive backpack carried by the setName and getName methods. No matter where the setName and getName methods are called, they will carry the exclusive backpack for the foo function.

As can be seen from the above, in JavaScript, according to the rules of lexical scope, the internal function can always access the variables declared in its external function, when an internal function is returned by calling an external function, even if the external function has been executed. , But the variables of internal functions referencing external functions are still stored in memory, and we call the collection of these variables a closure.

For example, the external function is foo, then the set of these variables is called the closure of the foo function.

V8 execution JavaScript code flow

V8 execute JavaScript code, you need to go through compiled and execution two phases:

  • The compilation process refers to the stage where V8 converts JavaScript code into bytecode or binary machine code;
  • The execution stage refers to the stage where the interpreter interprets and executes the bytecode, or the CPU directly executes the binary machine code.

Initialize the execution environment

Stack space and heap space

In Chrome, as long as you open a rendering process, the rendering process will initialize V8, and initialize the heap space and stack space at the same time.

Global execution context

If in the browser, JavaScript code will frequently manipulate window (this by default points to the window object), manipulate dom and other content, if in node, JavaScript will frequently use global (this by default points to the global object), File API and other content, these contents Will be prepared during the startup process, we call these contents global execution context .

In the browser environment, the global execution context includes the window object, the this keyword that points to the window by default, and some Web API functions, such as setTimeout, XMLHttpRequest, and so on.

The global execution context will not be destroyed during the life cycle of V8. It will always be stored in the heap, so that when you need to use a function or global variable next time, it does not need to be recreated.

In addition, when you execute a piece of global code, if there are declared functions or defined variables in the global code, then the function object and the declared variables will be added to the global execution context.

Global scope

When V8 starts, a global scope will be created. The global scope includes variables such as this and window, as well as some global Web API interfaces.

The relationship between global execution context and global scope

You can think of scope as an abstract concept. For example, in ES6, multiple scopes can exist in the same global execution context:

var x = 5
{
    let y = 2
    const z = 3
}

When this code is executed, there will be two corresponding scopes, one is the global scope, and the other is the scope inside the brackets, but these contents will be saved in the global execution context.

image-20210520154755352

Construct an event loop system

With the heap space and stack space, the global execution context and global scope are generated, can JavaScript code be executed next?

No, an event loop system needs to be constructed. The event loop system is mainly used to process task queuing and task scheduling.

Detailed content single open article

Compile stage

var name = 'Javascript'
var type = 'global'
function foo(){
var name = 'foo'
console.log(name)
console.log(type)
}
function bar(){
var name = 'bar'
var type = 'function'
foo()
}
bar()

Generate abstract syntax tree (AST)

A high-level language is a language that developers can understand, but it is very difficult for a compiler or interpreter to understand it. For compilers or interpreters, what they can understand is AST. So whether you are using an interpreted language or a compiled language, they will generate an AST during the compilation process. This is similar to the case where the rendering engine converts an HTML format file into a DOM tree that the computer can understand.

As can be seen from the figure, the structure of the AST is very similar to the structure of the code. In fact, you can also regard the AST as a structured representation of the code. The subsequent work of the compiler or interpreter needs to depend on the AST, not the source code. .

AST is a very important data structure, which is widely used in many projects. One of the most famous projects is Babel. Babel is a widely used code transcoder that can convert ES6 code to ES5 code, which means you can write programs in ES6 now without worrying about whether the existing environment supports ES6. The working principle of Babel is to first convert ES6 source code to AST, then convert ES6 syntax AST to ES5 syntax AST, and finally use ES5 AST to generate JavaScript source code.

In addition to Babel, ESLint also uses AST. ESLint is a plug-in used to check JavaScript writing specifications. The detection process also needs to convert the source code to AST, and then use the AST to check code standardization.

Now that you know what AST is and some of its applications, let's take a look at how AST is generated. Generally, two stages are required to generate AST.

first stage of 160a852cf5a9d8 is tokenize, also known as lexical analysis , whose function is to disassemble the source code line by line into tokens. The so-called token refers to the smallest single character or string that cannot be subdivided grammatically. You can refer to the figure below to better understand what token.

It can be seen from the figure that var myName = ' JavaScript ' , in which the keyword "var", the identifier "myName", the assignment operator "=", and the string "JavaScript" are all tokens, and they represent The attributes are not the same.

second stage of 160a852cf5aa3d is parse, also known as grammatical analysis . Its function is to convert the token data generated in the previous step into AST according to the grammatical rules. If the source code conforms to the grammatical rules, this step will be completed smoothly. But if there is a syntax error in the source code, this step will be terminated and a "syntax error" will be thrown.

This is the generation process of AST, the word segmentation first, and then the analysis.

After having the AST, then V8 will generate the execution context of the code.

Generate execution context and scope

image-20210521202335773

Generate bytecode

With the AST and execution context, the next second step, the interpreter Ignition will be on the scene, it will generate bytecode according to the AST, and interpret and execute the bytecode.

In fact, V8 did not have bytecode at the beginning, but directly converted AST into machine code. Since the efficiency of executing machine code is very efficient, this method will run very well within a period of time after its release.

However, with the widespread popularity of Chrome on mobile phones, especially mobile phones running on 512M memory, the memory occupation problem has also been exposed, because V8 needs a large amount of memory to store the converted machine code. In order to solve the memory occupancy problem, the V8 team significantly refactored the engine architecture, introduced bytecode, and abandoned the previous compiler, and finally it took four years to implement the current architecture.

So what is bytecode? Why can the introduction of bytecode solve the memory usage problem?

Bytecode is a kind of code between AST and machine code. But it has nothing to do with the specific type of machine code. The byte code needs to be converted into machine code by an interpreter before it can be executed.

After understanding what bytecode is, let’s compare the advanced code, bytecode and machine code. You can refer to the figure below

Execution phase

After generating the bytecode, the next step is to enter the execution phase.

The scope and execution context at this time:

image-20210521205932336

Generally, if there is a bytecode that is executed for the first time, the interpreter Ignition will interpret and execute it one by one. In the process of executing bytecode, if a hot spot code (HotSpot) is found, such as a piece of code that has been repeatedly executed multiple times, this is called hot spot code. Then the background compiler TurboFan will take the hot spot byte The code is compiled into efficient machine code, and then when this optimized code is executed again, only the compiled machine code needs to be executed, which greatly improves the execution efficiency of the code.

reference

Graphical Google V8

browser working principle and practice

[Translation] Understanding Javascript execution context and execution stack

Understanding Execution Context and Execution Stack in Javascript

front end


人丑就要多读书_
197 声望17 粉丝