Through previous articles, I believe you already understand the JavaScript is a language based on object, it can achieve a lot of characteristics, such as the function is first class citizens, closures, functional programming, prototypal inheritance, etc., to understand these features, we can now to open the V8 this black box, a thorough understanding of its compilation assembly line.

We know that when you want to execute a piece of JavaScript code, all you have to do is throw the code to the V8 virtual machine, and V8 will execute and return you the results.

Before executing the JavaScript code, V8 prepares the runtime environment for the code, including heap space and stack space, global execution context, global scope, built-in built-in functions, extension functions and objects provided by the host environment, and the message loop. Before V8 can execute JavaScript code, it can parse source code, generate bytecode, interpret execution, or compile execution.

Knowing enough about the runtime environment will help us better understand V8’s execution flow. For example, the event loop allows you to know how various callback functions are executed, the stack space allows you to know how functions are called, the heap space and stack space allows you to know why there are values and references passed, and so on.

The knowledge involved in the runtime environment is very basic, but very easy to overlook. In this article, we’ll examine these basic runtime environments.

What is the host environment?

To talk about the runtime environment running V8, we have to talk about the host environment of V8. What is the host environment of V8?

In biology, the host refers to the organism that provides the living environment for viruses and other parasites. The host has its own complete metabolic system, while the virus does not have its own metabolic system or enzyme system, but consists of long nucleic acid chains and protein shells.

So, in order for a virus to replicate itself, it shares a metabolic system with the host, and when it leaves the host cell, it becomes a chemical substance that has no life and cannot reproduce on its own. At the same time, if the virus uses too much of the host cell resources, it will affect the normal activities of the cell.

Similarly, you can think of V8’s relationship to the browser’s rendering process as that of a virus and a cell. The browser provides V8 with the basic message loop, global variables, Web apis, and V8’s core implementation of the ECMAScript standard, which is the virus’s own DNA or RNA. V8 provides only ecMAScript-defined objects and some core functions, including Object, Function, and String. V8 also provides basic features such as garbage collectors and coroutines, but these still require the cooperation of the host environment to fully implement.

Improper use of V8, such as irregular code that triggers frequent garbage collection, or a function that takes too long to execute, can tie up the main thread of the host environment, affecting program performance, or even causing the host environment to freeze.

In addition to the browser hosting V8, Node.js is a different hosting environment for V8, providing different hosting objects and hosting apis, but the overall flow remains the same. For example, Node.js also provides a message loop and a main runtime thread.

Relationship between host environment and V8

Ok, now we know that to execute V8, you need a host environment. The host environment can be a renderer in the browser, a Node.js process, or some other custom developed environment that provides many of the basic features V8 needs to execute JavaScript. Let’s take a look at each of these components.

Construct data storage space: heap space and stack space

Since V8 is hosted in browsers or node.js hosts, V8 is also launched by those hosts. For example, in Chrome, whenever a render process is opened, it initializes V8, both heap space and stack space.

The stack space is mainly used to manage JavaScript function calls. The stack is a contiguous space in memory, and the stack structure is a “first in, last out” strategy. During a function call, context-sensitive content is stored on the stack, such as the native type, the address of the referenced object, the execution state of the function, the this value, and so on. When a function completes execution, the execution context of that function is destroyed.

Stack space is the biggest characteristic of space is continuous, so the address of each element in the stack are fixed, so the stack space search efficiency is very high, but usually in memory, it is difficult to assign to a large contiguous space, therefore, V8 do to the size of the stack space limit, if a function call level too deep, the V8 will likely throw stack overflow error. You can execute the following code from the console:

function factorial(n){

if(n === 1) {return 1; }

return n*factorial(n-1);

}

console.log(factorial(50000))

Executing this code results in an error like this:

VM68:1 Uncaught RangeError: Maximum call stack size exceeded

A stack overflow error was raised because the factorial function required 50,000 levels of nested calls, and the stack did not provide that much space.

If you have data that takes up a lot of memory, or doesn’t need to be stored in contiguous space, using stack space is not a good idea, so V8 uses heap space again.

Heap space is a tree storage structure that is used to store discrete data of object types. As we discussed in previous lectures, in JavaScript, in addition to primitive types of data, all objects are object types, such as functions, arrays, and in browsers, window objects, document objects, etc. These are all in heap space.

When the host starts V8, it creates both heap space and stack space, where new data is stored as it proceeds.

Global execution context and global scope

After V8 initializes the underlying storage, it is time to initialize the global execution context and global scope, which are the basis for V8 to execute the subsequent process.

When V8 starts executing a piece of executable code, an execution context is generated. V8 uses the execution context to maintain variable declarations, this Pointers, and so on needed to execute the current code.

The execution context consists of three main parts, the variable context, the lexical context, and the this keyword. In the browser environment, for example, the global execution context includes the Window object, the this keyword that points to the window by default, and Web API functions such as setTimeout, XMLHttpRequest, and so on.

The lexical environment, on the other hand, contains the use of let, const, and so on.

For details of the execution context, you can refer to the following figure:

What is an execution context

The global execution context is not destroyed for the lifetime of V8; it remains in the heap so that the next time a function or global variable needs to be used, it does not need to be recreated. In addition, when you execute a piece of global code, if the global code has declared functions or defined variables, both the function object and the declared variables are added to the global execution context. Take this code for example:

var x = 1

function show_x(){

console.log(x)

}

As V8 executes this code, it adds the variable x and the function show_x to the global execution context.

In ES6, for example, there can be multiple scopes in the same global execution context. You can look at the following code:

var x = 5

{

let y = 2

const z = 3

}

When this code executes, it will have two corresponding scopes, one global and one inside the parentheses, but these contents will be saved in the global execution context. You can refer to the picture below:

Global and subscope relationships

When V8 calls a function, it enters the execution context of the function, where the global execution context and the current function execution context form a stack structure. For example, execute the following code:

var x = 1

function show_x(){

console.log(x)

}

function bar(){

show_x()

}

bar()

When executing show_x, the stack state is shown below:

Function call stack

Construct the event loop system

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

The answer is no, because V8 also needs a main thread that executes JavaScript and performs garbage collection, among other things. V8 is parasitic in the host environment. It does not have its own main thread, but uses the main thread provided by the host, where all the code executed by V8 is executed.

There is only one main thread still doesn’t work, because if you open a thread, performing a piece of code in this thread, so when the thread after executing the code, you will automatically quit, some of the implementation process on the stack data also will be destroyed, when performing another piece of code, the next time you are also required to start a thread, to initialize the stack data, This can seriously affect the performance of the program at execution time.

To keep the thread running after executing code, it is common to add a loop to the code that listens for the next event. For example, if you want to execute another statement, activate the loop to execute it. For example, the following simulation code:

while(1){

Task Task = GetNewTask();

RunTask (task);

}

This code uses a loop to get a new task differently and execute it as soon as a new task is available.

If the main thread is executing a task, then here comes a new task, such as the V8 is DOM operation, then the browser’s network thread to complete tasks for a page to load, and V8 registered monitored the download is complete, then this case will need to bring in a message queue, to let the download is complete event staging to the message queue, Wait until the current task completes, and then fetch the queued task from the message queue. After a task has been executed, our event loop repeats the process, continuing to fetch from the message queue and execute the next task.

One thing you should be aware of is that since all tasks run on the main thread, V8 shares the main thread and message queue with the page in the browser, so if V8 executes a function for too long, it will affect the performance of the browser page.

conclusion

Since V8 is not a complete system, part of its base environment is provided by the host at execution time, including the global execution context, the event loop system, the heap space, and the stack space. V8 itself provides the core functionality of JavaScript and a garbage collection system, in addition to the basic environment that the host provides.

During startup, the host environment constructs a heap space to hold some object data and a stack space to hold native data. Because the heap stored in the data is not linear, so heap space can store a lot of data, but reading speed will be slow, and stack space is continuous, so the heap space search speed is very fast, but to find a continuous area in memory but seem a bit difficult, so all the program limit the size of the stack space, This is one of the main reasons we are prone to stack overflows.

In a browser, JavaScript code frequently manipulates window(this by default refers to the window object), dom, etc. In node, JavaScript makes frequent use of global(this by default refers to the Global object), the File API, etc., all of which are prepared during startup, and we call these global execution contexts.

Global execution context and the function of the execution context is different from the life cycle, the function execution context, at the end of the function is destroyed, while global execution context and the lifecycle of the V8 is consistent, so in actual project, if you don’t often use the variables or data, it is best not to in the global execution context.

In addition, the host environment also needs to construct an event loop system, which is mainly used to deal with task queuing and task scheduling.