I’ll introduce you to two concepts, compilers and interpreters:

Compiler and interpreter

Compilers and interpreters exist because machines cannot directly understand the code we write, so we need to “translate” the code we write into machine language that machines can understand before we can execute the program. According to the execution process of languages, languages can be divided into compiled languages and interpreted languages.

Compiled languages require the compiler to compile the program before it can be executed, and after the compilation, the machine-readable binary is kept so that it can be run each time the program is run without having to recompile it. C/C++, GO, etc are compiled languages.

The programs written by interpreted languages need to be dynamically interpreted and executed by an interpreter every time they are run. Python, JavaScript, etc., are interpreted languages.

How do compilers and interpreters “translate” code? You can refer to the picture below for the specific process:

! [](” Always > how does V8 execute JS code? > image2021-3-16_20-57-0.png”)

Compilers and interpreters “translate” code

From the figure, you can see the execution process of the two, which can be roughly described as follows:

  1. In the process of compiling a compiled language, the compiler first conducts lexical analysis of the source code, then syntactic analysis to generate an abstract syntax tree (AST), then optimizes the code, and finally generates machine code that the processor can understand. If the compilation succeeds, an executable file is generated. But if a syntax or other error occurs during compilation, the compiler will throw an exception and the resulting binary will not be generated successfully.
  2. In interpreted languages, the interpreter also performs lexical analysis and syntax analysis of the source code, and generates an abstract syntax tree (AST). However, it generates bytecodes based on the abstract syntax tree, and finally executes programs and outputs results based on the bytecodes.

How does V8 execute a piece of JavaScript code

From the above introduction, I believe you already know about compilers and interpreters. Let’s focus on how V8 executes a piece of JavaScript code. You can start by “seeing the big picture”, as shown below:

! [](” Always > how does V8 execute JS code? > image2021-3-16_20-58-47.png”)

As you can see from the diagram, V8 has both Ignition and TurboFan, so how do they work together to execute a piece of JavaScript code? Let’s break down the execution process one by one according to the figure above.

1. Generate abstract syntax tree (AST) and execution context

Convert the source code into an abstract syntax tree and generate an execution context, which we’ve covered a lot in previous articles, mainly information about the environment in which the code is being executed.

So let’s focus on the abstract syntax tree (which is simply called AST for short in the following statements) to see what an AST is and how it is generated.

A high-level language is one that developers can understand, but one that compilers or interpreters can’t. For a compiler or interpreter, the AST is all they can understand. So whether you’re using an interpreted language or a compiled language, they all generate an AST during compilation. This is similar to how a rendering engine converts an HTML-formatted file into a DOM tree that a computer can understand.

You can use this code to get a sense of what an AST is:

\

| ` ` ` var myName = “geek” time function foo () {return 23; } myName = “geektime” foo()

| -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | \ This code after [javascript - ast] (http://resources.jointjs.com/demos/javascript-ast) site, generate ast structure is as follows:! [](" Always > how does V8 execute JS code? > image2021-3-16_21-10-17.png") the structure of the AST is very similar to the structure of the code. In fact, you can think of the AST as a structured representation of the code. Not the source code. In general, there are two stages to generate an AST. The first stage is lexical analysis, which breaks down lines of source code into tokens. **token** refers to the smallest single character or string that is syntactically impossible to divide. You can refer to the following figure to better understand what tokens are. ! [](" Always > how does V8 execute JS code? > image2021-3-16_21-13-29.png") \ 'var myName =' geek time '; The keyword "var", identifier "myName", assignment operator "=", and string "geek time" are all tokens, and they represent different attributes. ** The second stage is syntax analysis **, which is used to convert token data generated in the previous step into AST according to syntax rules. If the source code is syntactically correct, this step is done smoothly. But if there is a syntax error in the source code, this step terminates and a "syntax error" is thrown. This is how the AST is generated, first word segmentation, then parsing. With the AST in place, V8 will then generate the execution context for that code. Now that you have the AST and the execution context, the next step, the interpreter Ignition, is to generate the bytecode based on the AST and interpret and execute the bytecode. \ What is bytecode? Why should the introduction of bytecode solve the memory footprint problem? ** Bytecode is a type of code between AST and machine code. Bytecode needs to be translated into machine code by the interpreter before it can be executed. Now that ** understands what bytecode is, let's compare high-level code, bytecode, and machine code. You can refer to the following image: [](" Always > how does V8 execute JS code? > image2021-3-16_21-17-52.png") as you can see from the figure, machine code takes up much more space than bytecode, so using bytecode can reduce the memory usage of the system. ### 3. After executing the code to generate bytecode, the next step is execution. Typically, if you have a piece of bytecode being executed for the first time, the interpreter Ignition interprets the execution line by line. If a HotSpot is found during bytecode execution, such as a piece of code that has been repeatedly executed, TurboFan compiles the bytecode into efficient machine code, and when the optimized code is executed again, It only needs to execute the compiled machine code, which greatly improves the efficiency of code execution. In fact, bytecode with interpreter and compiler is a hot technology recently, such as Java and Python virtual machines are implemented based on this technology, we call this technology just-in-time compilation (JIT) **. In V8's case, the interpreter Ignition collects code information while interpreting and executing the bytecode, and when it sees a section of code getting hot, the TurboFan compiler springs to life, converting the hot bytecode into machine code and saving it for future use. So many languages use "bytecode +JIT" technology, so it is necessary to understand the JIT system. You can see how JIT works by combining the following diagram: [](" Always > how does V8 execute JS code? > image2021-3-16_21-21-22.png") ## image2021-3-16_21-21-22.png Improve the execution speed of a single script, avoid the long task of JavaScript to occupy the main thread, so that the page can quickly respond to interaction; 1. Avoid large inline scripts, as parsing and compiling also occupy the main thread while parsing HTML; 1. Reduce the size of JavaScript files, as smaller files increase download speed and use less memory.Copy the code