We all know that JavaScript is a single-threaded language, and yes, it’s a single-threaded language, but have you ever wondered why JavaScript can execute functions and timers at the same time?

1. Processes and threads

1.1 Process (Process)

Is a computer program about a data set on a running activity, is the system for resource allocation and scheduling of the basic unit, is the basis of the operating system structure. In modern thread-oriented computer architectures, processes are containers for threads. A program is a description of instructions, data and their organizational form, while a process is an entity of the program. Is a computer program about a data set on a running activity, is the system for resource allocation and scheduling of the basic unit, is the basis of the operating system structure. A program is a description of instructions, data and their organizational form, while a process is an entity of the program.

A process is likened to a factory floor, representing a single task that a CPU can handle. At any one time, the CPU is always running one process and the other processes are not running.

1.2 Threads

Is the smallest unit in which an operating system can schedule operations. It is contained within the process and is the actual operating unit within the process. A thread is a single sequential flow of control in a process, and multiple threads can be concurrent in a process, each performing a different task in parallel.

Here, threads are compared to workers in a workshop, that is, a workshop can allow multiple workers to cooperate to complete a task.

2. Multithreaded browser

I have written a “front-end development you should know browser knowledge”, you can have a look at the time.

The browser kernel is multi-threaded, under the control of the kernel threads cooperate with each other to keep synchronized, a browser usually consists of the following resident threads:

  • GUI rendering thread
  • JavaScript engine threads
  • Event trigger thread
  • Timing trigger thread
  • Asynchronous HTTP request threads

We see the JS engine thread, very familiar, no mistake, this is where we execute the javascript script.

The JS engine is multi-threaded. Single-threaded means that the JS engine only assigns a thread to it when it executes JS. It means that the JS engine assigns a thread to JavaScript for execution, which is what we call single thread.

2.1 Here is the JS execution mechanism

Since JavaScript is single-threaded (there is only one JS thread running a JavaScript program at any one time in a Tab page).

So we need to rely on task queues for JavaScript code execution.

The JS engine waits for the task to arrive in the task queue and then executes the task.

Of course, synchronous tasks can be executed this way, we put the tasks in the task queue, one by one, the logic is very clear. However, if we send a request to the background, the time between sending and receiving might take a second, so we can’t wait for a second, can we wait for five requests, five seconds? The display does not meet our requirements, so we need asynchronous tasks to handle this problem.

2.2 Synchronous and Asynchronous Tasks

A synchronization task is a task that is queued up for execution on the main thread. The next task can only be carried out after the previous task is completed. When we open a website, the rendering process of the website, such as the rendering of elements, is actually a synchronization task

An asynchronous task is a task that does not enter the main thread, but enters the task queue. Only when the task queue informs the main thread that an asynchronous task is ready to execute, the task will enter the main thread. When we open the website, loading images, loading music, is actually an asynchronous task.

You must have a more concrete understanding of Event Loop. I will not elaborate on it here. If you don’t understand, please tell me and I will talk about it later.

3. The main points of this article can be read directly

But is there any question about task queues? This is an object, right? It’s an array, right? According to my logic, our JavaScript main thread executes a synchronous function, and the asynchronous function can be placed in a task queue, and the task queue can be an object, and when we’re done executing the synchronous task, we can just push that object into the main thread, but that’s not what I want.

Evnet Loop task queue on the browser’s event trigger thread, asynchronous function that should execute when the JS engine, will put an asynchronous task event trigger in the thread, while the corresponding asynchronous tasks in line with the trigger condition is triggered, event trigger thread will add asynchronous tasks to the main thread of the JS engine, awaiting execution.

Is it different from what we think of as a single threaded JavaScript? Well, it’s not quite the same, so in the end, we’re talking about a task queue that’s actually a thread.

And then, back to the timer that we talked about at the beginning, you can almost guess, it’s controlled by the timer thread.

Because JavaScript is single-threaded, it is necessary to open a separate thread for timing if it is blocked.

When using setTimeout or setInterval, it requires the timer thread to time, which pushes the specific event into the event queue.

4. Conclusion

So, we say JavaScript is single-threaded and it’s single-threaded, but our Event loops and timers are in other threads.

5. V8 — Expansion

V8 engine is a JavaScript engine implementation originally designed by some language experts, acquired by Google, and subsequently open-source by Google.

V8 is developed in C++ and compiles it to native machine code (ia-32, x86-64, ARM, or MIPS CPUs) before running JavaScript, compared to other JavaScript engines that convert it to bytecode or interpreted execution. Methods such as inline caching are used to improve performance.

With these features, JavaScript programs run as fast as binary programs in V8. V8 supports many operating systems, such as Windows, Linux, Android, etc., and other hardware architectures, such as IA32,X64,ARM, etc., with good portability and cross-platform features.

5.1 Workflow

The V8 engine has two main stages in the execution of JavaScript: compilation and execution. Unlike C++, JavaScript needs to be compiled and executed while the user is using it. In V8, javascript-related code is compiled not overnight, but only when something needs to be executed, which improves response times and reduces time overhead. In V8, the source code is turned into an abstract syntax tree (AST) by the parser, and then native executable code is generated directly from the AST using the JIT compiler’s full code generator. This process is different from Mr. JAVA to bytecode or intermediate representation, reducing AST to bytecode conversion time and improving code execution speed. But the lack of an intermediate process of conversion to bytecode reduces the opportunity to optimize the code.

The main classes used by the V8 engine to compile native code are as follows:

  • Script: represents JavaScript code, which contains source code and local code generated after compilation.
  • Compiler: a Compiler class, supplemented by a Script class to compile the generated code, calls the Parser to generate the AST and a full-code generator to convert the AST into native code;
  • AstNode: abstract syntax tree node class, which is the base class for all other nodes. It contains many subclasses, and then generates different native code for different subclasses.
  • AstVisitor: A visitor class for an abstract syntax tree that iterates through a heterogeneous abstract syntax tree
  • FullCodeGenerator: A subclass of the AstVisitor class that generates native executable code for JavaScript by iterating through the AST.

The process of compiling JavaScript code is roughly as follows: the Script class calls the Compile function of the Compiler class to generate native code for it. The Compile function uses the Parser class to generate the AST and the FullCodeGenerator class to generate native code. Native code is closely related to the specific hardware platform, and FullCodeGenerator uses multiple backends to generate platform-appropriate native assembly code. Since FullCodeGenerator traverses the AST to generate assembly code for each node, the global view is missing and optimization between nodes is not possible.