The operating mechanism of JavaScript

Processes and threads

What is a process

The CPU is the heart of the computer and undertakes all computing tasks.
According to the official website, a process is the smallest unit of CPU resource allocation.
It literally means a program in progress. I understand it as a task program that can run independently and has its own resource space. The process includes the running program and the memory and system resources used by the program.
The CPU can have many processes. Our computer generates one or more processes every time we open a software. Why does the computer get stuck when there are too many softwares running? It is because the CPU allocates resource space to each process, but there are only so many in one CPU. The more resources are allocated, the more stuck they are. Each process is independent of each other. When the CPU is running one process, other processes are in a non-running state. The CPU uses the time slice round-robin scheduling algorithm to run multiple processes at the same time.

What is a thread

Thread is the smallest unit of CPU scheduling.
A thread is a program running unit based on a process. In layman’s terms, a thread is an execution flow in a program, and a process can have multiple threads.
Only one execution flow in a process is called a single thread, that is, when the program is executed, the program paths taken are arranged in a continuous order, and the previous ones must be processed well before the latter ones will be executed.
Multiple execution streams in a process are called multithreading, that is, multiple different threads can run simultaneously in a program to perform different tasks, that is to say, a single program is allowed to create multiple parallel execution threads to complete their respective tasks .

The difference between process and thread

A process is the smallest unit for resource allocation by the operating system, and a thread is the smallest unit for program execution.
A process consists of one or more threads, and threads can be understood as different execution routes of code in a process.
Processes are independent of each other, but each thread under the same process shares the memory space of the program (including code segments, data sets, heaps, etc.) and some process-level resources (such as open files and signals).
Scheduling and Switching: Thread context switching is much faster than process context switching.

Multiple processes and threads

  • Multi-process: Multi-process refers to if two or more processes are allowed to run in the same computer system at the same time. The benefits brought by multi-process are obvious. For example, you can open the editor to type code while listening to music on NetEase Cloud, and the processes of the editor and NetEase Cloud will not interfere with each other;
  • Multi-threading: Multi-threading refers to a program that contains multiple execution streams, that is, multiple different threads can run simultaneously in a program to perform different tasks, that is to say, a single program is allowed to create multiple parallel execution threads to complete their respective tasks. task;

Why JS is single-threaded

The single thread of JS is related to its purpose.
As a browser scripting language, the main purpose of JavaScript is to interact with users and manipulate the DOM. This determines that it can only be single-threaded, otherwise it will bring very complicated synchronization problems. For example, assuming that JavaScript has two threads at the same time, one thread adds content to a certain DOM node, and the other thread deletes this node, which thread should the browser take as the basis?
Some people say that js also has Worker threads. Yes, in order to take advantage of the computing power of multi-core CPUs, HTML5 proposes the Web Worker standard, which allows JavaScript scripts to create multiple threads, but sub-threads are completely controlled by the main thread and must not operate DOM.
Therefore, this standard does not change the essence of JavaScript being single-threaded.

Browser

Take Chrome as an example, every time we open a Tab page, a process will be generated.

What processes does the browser contain

  1. Browser process
    a. The main process of the browser (responsible for coordination and master control), there is only one process;
    b. Responsible for browser interface display and interaction with users. Such as forward, backward, etc.;
    c. Responsible for the management of each page, creating and destroying other processes;
    d. Draw the Bitmap (bitmap) in the memory obtained by the rendering (Renderer) process to the user interface;
    e. Management of network resources, downloading, etc.;
  2. Third-party plug-in process
    a. Each type of plug-in corresponds to a process, which is created when the plug-in is used;
  3. GPU process
    a. There is only one process, which is used for 3D drawing, etc.;
  4. rendering process
    a. The so-called browser kernel (Renderer process, internally multi-threaded);
    b. Each Tab page has a rendering process that does not affect each other;
    c. The main functions are page rendering, script execution, event processing, etc.;

Why do browsers need multi-process

Assuming that the browser is a single process, then if a tab page crashes, it will affect the entire browser. How bad is the experience? In the same way, if the plug-in crashes, it will also affect the entire browser.
There are many browser processes, and each process has many threads, which will occupy memory

Rendering process

Page rendering, JS execution, and event loop are all executed in the rendering process, so we need to focus on understanding the rendering process
The rendering process is multi-threaded, see some commonly used and main threads of the rendering process:

GUI rendering thread

  1. Responsible for rendering browser interface, parsing HTML, CSS, building DOM tree and RenderObject tree, layout and drawing, etc.;
    a. Parse the html code (HTML code is essentially a string) and convert it into a node recognized by the browser to generate a DOM tree, that is, a DOM Tree;
    b. Parse css and generate CSSOM (CSS rule tree);
    c. Combine DOM Tree and CSSOM to generate Rendering Tree (rendering tree);
  2. When we modify the color or background color of some elements, the page will be redrawn (Repaint);
  3. When we modify the size of the element, the page will reflow (Reflow);
  4. When the page needs Repaing and Reflow, the GUI thread executes and draws the page;
  5. The cost of reflow (Reflow) is higher than that of repaint (Repaint), we should try to avoid Reflow and Repaint;
  6. GUI rendering thread and JS engine thread are mutually exclusive:
    a. When the JS engine executes, the GUI thread will be suspended (equivalent to being frozen);
    b. GUI updates will be stored in a queue and executed immediately when the JS engine is idle;

JS engine thread

  1. The JS engine thread is the JS kernel, which is responsible for processing Javascript script programs (such as V8 engine);
  2. The JS engine thread is responsible for parsing Javascript scripts and running codes;
  3. The JS engine has been waiting for the arrival of tasks in the task queue, and then processed them:
    a. The browser can only have one JS engine thread running the JS program at the same time, so js runs in a single thread;
    b. In a Tab page (renderer process), only one JS thread is running the JS program at any time;
  4. The GUI rendering thread and the JS engine thread are mutually exclusive, and the js engine thread will block the GUI rendering thread
    a. It is the JS execution time that we often encounter is too long, resulting in incoherent rendering of the page, resulting in blocking of page rendering and loading (that is, slow loading);
    b. For example, when the browser renders

Event trigger thread

  1. Belongs to the browser rather than the JS engine, is used to control the event loop, and manages an event queue (task queue);
  2. When js execution encounters event binding and some asynchronous operations (such as setTimeOut, or other threads from the browser kernel, such as mouse clicks, AJAX asynchronous requests, etc.), the event trigger thread will be used to add the corresponding event to the corresponding thread (such as timer operation, add the timer event to the timer thread), wait for the asynchronous event to have a result, add their callback operation to the event queue, and wait for the js engine thread to process when it is idle;
  3. When the corresponding event meets the trigger conditions and is triggered, the thread will add the event to the end of the queue to be processed, waiting for the JS engine to process;
  4. Because JS is single-threaded, the events in these pending queues have to be queued for processing by the JS engine;

Timed trigger thread

  1. The thread where setInterval and setTimeout are located;
  2. The browser timing counter is not counted by the JavaScript engine (because the JavaScript engine is single-threaded, if it is in a blocked thread state, it will affect the accuracy of the timing);
  3. Timing and timing are triggered by a separate thread (after the timing is completed, it is added to the event queue of the event trigger thread, and executed after the JS engine is idle). This thread is a timing trigger thread, also called a timer thread;
  4. W3C stipulates in the HTML standard that the time interval lower than 4ms in setTimeout is required to be counted as 4ms;

Asynchronous http request thread

  1. After XMLHttpRequest is connected, a new thread request is opened through the browser;
  2. When a state change is detected, if a callback function is set, the asynchronous thread will generate a state change event, put this callback into the event queue and then be executed by the JavaScript engine;
  3. Simply put, when an http asynchronous request is executed, the asynchronous request event is added to the asynchronous request thread, and when the response is received (accurately, it should be the http state change), the callback function is added to the event queue and waits for the js engine thread to execute;

Basics of Event Loop

JS is divided into synchronous tasks and asynchronous tasks. Synchronization tasks are executed on the main thread (the main thread here is the JS engine thread), which will form an execution stack.
In addition to the main thread, the event trigger thread manages a task queue. As long as the asynchronous task has a running result, an event callback is placed in the task queue. Once all synchronous tasks in the execution stack are executed (that is, the JS engine thread is idle), the system will read the task queue and call back the runnable asynchronous tasks (events in the task queue, as long as there are event callbacks in the task queue, It means that it can be executed) is added to the execution stack and starts to execute
Let’s look at a simple piece of code:

let setTimeoutCallBack = function() {<!-- -->
  console.log('I am a timer callback');
};
let httpCallback = function() {<!-- -->
  console.log('I am http request callback');
}

// sync task
console.log('I am synchronous task 1');

// asynchronous timing task
setTimeout(setTimeoutCallBack,1000);

// asynchronous http request task
ajax.get('/info', httpCallback);

// sync task
console.log('I am synchronous task 2');

The execution process of the above code:

  1. JS is executed sequentially from top to bottom. It can be understood that the execution environment of this code is the main thread, which is the current execution stack;
  2. First, execute console.log(‘I am synchronous task 1’);
  3. Then, when setTimeout is executed, it will be handed over to the timer thread, and the timer thread will be notified to hand over the setTimeoutCallBack callback to the event trigger thread for processing after 1 second. After 1 second, the event trigger thread will receive the setTimeoutCallBack callback and add it to the event trigger Waiting for execution in the event queue managed by the thread;
  4. Next, the execution of the http request will be handed over to the asynchronous http request thread to send the network request. After the request is successful, the callback httpCallback will be handed over to the event-triggered thread for processing. After the event-triggered thread receives the httpCallback callback, it will be added to the event-triggered thread. Waiting for execution in the event queue;
  5. Then execute console.log(‘I am synchronization task 2’);
  6. At this point, the execution of the main thread execution stack is completed, the JS engine thread is idle, and it starts to inquire about the event trigger thread, asking whether there is a callback function that needs to be executed in the event queue of the event trigger thread, and if so, add the callback event in the event queue for execution In the stack, the callback starts to be executed. If there is no callback in the event queue, the JS engine thread will keep inquiring until there is one;

It can be found:

  1. The timing trigger thread only manages the timer and only pays attention to the timing and does not care about the result. When the timing ends, the callback is thrown to the event trigger thread;
  2. The asynchronous http request thread only manages the http request and does not care about the result. When the request ends, the callback is thrown to the event trigger thread;
  3. The event trigger thread only cares about the asynchronous callback into the event queue;
  4. The JS engine thread will only execute the events in the execution stack. After the code in the execution stack is executed, it will read the events in the event queue and add them to the execution stack to continue execution;
  5. Repeated execution is what we call the event loop (Event Loop);

  1. The execution stack starts sequential execution;
  2. Judging whether it is synchronous, or asynchronous, it enters the asynchronous thread, and finally the event is called back to the task queue of the event-triggered thread to wait for execution, and the execution continues synchronously;
  3. The execution stack is empty, and asks whether there is an event callback in the task queue;
  4. If there is an event callback in the task queue, add the callback to the end of the execution stack and continue to execute from the first step;
  5. If there is no event callback in the task queue, it will keep inquiring;

Macrotask & Microtask

Macrotask

In ECMAScript, macrotask is also called task.
We can regard the code executed by each execution stack as a macro task (including each time an event callback is obtained from the event queue and put into the execution stack for execution), each macro task will be executed from beginning to end, No other will be executed.
Since the JS engine thread and the GUI rendering thread are mutually exclusive, in order to make the macro tasks and DOM tasks proceed in an orderly manner, the browser will start the GUI rendering thread after the execution of a macro task and before the execution of the next macro task. , to render the page:

Macro Tasks -> GUI Rendering -> Macro Tasks -> ...

Common macro tasks:

  1. main code block;
  2. setTimeout;
  3. setInterval;
  4. setImmediate() – Node;
  5. requestAnimationFrame() – the browser

Microtask

ES6 newly introduced the Promise standard, and at the same time, the browser implemented an additional concept of microtask microtasks. In ECMAScript, microtasks are also called jobs.
We already know that after the end of the macro task, the rendering will be performed, and then the next macro task will be executed, and the micro task can be understood as the task executed immediately after the execution of the current macro task.
When a macro task is executed, all micro tasks generated during the execution will be executed before rendering:

Macrotask -> Microtask -> GUI Rendering -> Macrotask -> ...

Common Microtasks

  1. process.nextTick() – Node;
  2. Promise. then();
  3. catch;
  4. finally;
  5. Object. observe;
  6. MutationObserver;

Distinguish between macrotasks & microtasks

Open a new blank window and enter the following code in the console

window.open();

document.body.style = 'background:black';
document.body.style = 'background:red';
document.body.style = 'background:blue';
document.body.style = 'background:pink';

The background is directly rendered in pink. According to the above, the browser will first execute a macro task, then execute all micro tasks in the current execution stack, and then hand over the GUI rendering. The above four lines of code belong to the same macro task, and all of them are executed Rendering will be performed after the rendering is complete. When rendering, the GUI thread will optimize and merge all UI changes, so visually, you will only see the page turn pink.

document.body.style = 'background:blue';
setTimeout(()=>{<!-- -->
    document.body.style = 'background: black'
},200)

The page will be stuck in blue first, and then turn into a black background. The reason why the blue color is stuck is because the above code belongs to two macro tasks. The code executed by the first macro task is to turn the background into blue, then trigger rendering, turn the page into blue, and then trigger the second time The macro task turns the background black.

document.body.style = 'background:blue'
console. log(1);
Promise.resolve().then(()=>{<!-- -->
    console. log(2);
    document.body.style = 'background:pink'
});
console. log(3);

The output 1 3 2 is because the callback function of the then method of the promise object is executed asynchronously, so 2 is finally output
The background color of the page turns pink directly without going through the blue stage, because we set the background to blue in the macro task, but executed the micro task before rendering, and changed the background to Pink before performing the rendering.

Attention

  1. The browser will execute a macrotask first, then execute the microtask generated by the current execution stack, render, and then execute the next macrotask;
  2. Microtasks and macrotasks are not in the same task queue, not in the same task queue:
    a. For example, setTimeout is a macro task, its event callback is in the macro task queue, Promise.then() is a micro task, its event callback is in the micro task queue, the two are not a task queue;
    b. Taking Chrome as an example, everything related to rendering is executed in the rendering process. The tasks in the rendering process (DOM tree construction, js parsing, etc.) that require the main thread to execute will be executed in the main thread, while the browser A set of event loop mechanism is maintained. The tasks on the main thread will be executed in the message queue. The main thread will cycle the message queue and take out tasks from the head for execution. If other tasks are generated during the execution process and need to be executed by the main thread, Other threads in the rendering process will put the task at the end of the message queue, and the tasks in the message queue are all macro tasks;
    c. How are microtasks generated? When the script script is executed, the js engine will create an execution context for the global, and maintain a microtask queue in the execution context. When a microtask is encountered, the microtask callback will be placed in the microqueue. When all After the js code is executed, the engine will check the queue before exiting the global context, execute it if there is a callback, and exit the execution context if there is no callback. This is why micro tasks are earlier than macro tasks, and it is often said that each macro Each task has a microtask queue (since the timer is the API of the browser, the timer is a macrotask, and when encountering a timer in js, it will also be put into the queue of the browser);
  3. First execute a macro task, and then judge whether there is a micro task after execution;
  4. If there are microtasks, execute all microtasks first, and then render; if there are no microtasks, render directly;
  5. Then proceed to execute the next macro task;

Complete Event Loop

  1. When the overall script (as the first macro task) starts to execute, all the code will be divided into two parts: synchronous task and asynchronous task. Task;
  2. The macro task enters the Event Table and registers a callback function in it. Whenever the specified event is completed, the Event Table will move this function to the Event Queue;
  3. The microtask will also enter another Event Table and register a callback function in it. Whenever the specified event is completed, the Event Table will move this function to the Event Queue;
  4. When the tasks in the main thread are executed and the main thread is empty, the Event Queue of the microtask will be checked, if there are tasks, all will be executed, if not, the next macrotask will be executed;
  5. The above process will be repeated continuously, which is the Event Loop;

Promise &async/aweait

Promise

In new Promise(() => {}).then(), the front part of new Promise() is a constructor, which is a synchronous task, and the latter .then() is an asynchronous microtask:

new Promise((resolve) => {<!-- -->
console. log(1)
  resolve()
}).then(()=>{<!-- -->
console. log(2)
})
console. log(3)
// The above code outputs 1 3 2

async/await function

Async/await is essentially some encapsulation based on Promise, and Promise is a kind of microtask
Therefore, using the await keyword has a similar effect to Promise.then. The code before await is equivalent to the synchronous code with new Promise, and the code after await is equivalent to the asynchronous code of Promise.then.

setTimeout(() => console. log(4))

async function test() {<!-- -->
  console. log(1)
  await Promise. resolve()
  console. log(3)
}

test()

console. log(2)
// output 1 2 3 4

demo

function test() {<!-- -->
  console. log(1)
  setTimeout(function () {<!-- --> // timer1
    console. log(2)
  }, 1000)
}

test();

setTimeout(function () {<!-- --> // timer2
  console. log(3)
})

new Promise(function (resolve) {<!-- -->
  console. log(4)
  setTimeout(function () {<!-- --> // timer3
    console. log(5)
  }, 100)
  resolve()
}).then(function () {<!-- -->
  setTimeout(function () {<!-- --> // timer4
    console. log(6)
  }, 0)
  console. log(7)
})

console. log(8)

// output 1, 4, 8, 7, 3, 6, 5, 2
  1. JS is executed sequentially from top to bottom;
  2. Execute to test(), the test method is synchronous, directly execute console.log(1) and print 1;
  3. In the test method, setTimeout is an asynchronous macro task, and we call it timer1 and put it in the macro task queue;
  4. There is a setTimeout under the test method as an asynchronous macro task, and we call it timer2 and put it in the macro task queue;
  5. Execute promise, new Promise is a synchronous task, execute directly, print 4;
  6. The setTimeout in new Promise is an asynchronous macro task, and the callback is recorded as timer3 and placed in the macro task queue;
  7. Promise.then is a microtask, put it in the microtask queue;
  8. console.log(8) is a synchronous task, executed directly, and prints 8;
  9. After the main thread task is executed, check that there is Promise.then in the microtask queue;
  10. Start to execute microtasks, and find that setTimeout is an asynchronous macrotask, record it as timer4 and put it in the macrotask queue;
  11. console.log(7) in the microtask queue is a synchronous task, executed directly, and prints 7;
  12. After the microtask is executed, the first cycle ends;
  13. Check the macro task queue, there are timer1, timer2, timer3, timer4, four timer macro tasks, according to the timer delay time to get the order that can be executed, that is, Event Queue: timer2, timer4, timer3, timer1, take them out and put them in order Execute at the end of the execution stack;
  14. Execute timer2, console.log(3) is a synchronous task, execute it directly, and print 3;
  15. Check that there are no microtasks, and the second Event Loop ends;
  16. Execute timer4, console.log(6) is a synchronous task, execute it directly, and print 6;
  17. Check that there are no microtasks, and the third Event Loop ends;
  18. Execute timer3, console.log(5) synchronization task, execute directly, print 5;
  19. Check that there are no microtasks, and the fourth Event Loop ends;
  20. Execute timer1, console.log(2) synchronization task, execute directly, print 2;
  21. Check that there are no microtasks or macrotasks, and the fifth Event Loop ends;

Operation mechanism in NodeJS

Although the JavaScript operating environment in NodeJS is also V8 and single-threaded, there are still some differences from the performance in the browser.
In fact, the difference between nodejs and browsers is that nodejs macro tasks are divided into several types, and these several types have different task queues, and different task queues have different orders, while micro tasks are interspersed in each between macro tasks.
In the node environment, process.nextTick has a higher priority than Promise. It can be simply understood that after the end of the macro task, the nextTickQueue part in the micro task queue will be executed first, and then the Promise part in the micro task will be executed.

Event Loop of NodeJS:

  1. Node will first execute all MacroTasks of type timers, and then execute all MicroTasks (NextTick exception);
  2. Enter the poll phase, execute almost all MacroTasks, and then execute all MicroTasks;
  3. Then execute all MacroTasks of type check, and then execute all MicroTasks;
  4. Then execute all MacroTasks of type close callbacks, and then execute all MicroTasks;
  5. At this point, complete a Tick, return to the timers stage, and repeat the execution;