preface
People who are better than you are working harder than you. How can you go home from work and just sit there and watch Tiktok and TV shows? Living in your 60s in your 20s…
I believe that no matter what industry they are in, people who are good at learning and summarizing can always have a variety of opportunities. If they are not interested enough, they can only rely on self-discipline.
I believe that many partners, whether in the interview, or in the daily work must encounter some questions like this:
- Interviewer: What is your understanding of how JS works?
- SetTimeout is written before console.log(). Why is console.log() executed first?
In the face of the above problems, I believe that many partners are usually a little understanding, or probably know what is js event loop, what is asynchronous task with synchronous task, but if once dig the principle behind it, may be a face confused? Like the little brother below…
This article roughly from the following aspects to in-depth analysis of JS operating mechanism in the browser:
- The relationship between browser multiprocesses and JS engine threads
- How is the division of labor and the order of execution different between synchronous and asynchronous tasks in Event Loop
- Combine with common interview questions to analyze js operation mechanism
- What is the difference between browser-side and Node-side JS running mechanism
Browser multi – process what
Before we get into browser processes, let’s take a look at what a process is and what its characteristics are.
- Minimum execution environment process: When a program is running, it needs an execution environment, including execution context, code, data, and a main thread to execute tasks. When the program starts, the operating system allocates a chunk of memory for the program to initialize the running environment, which is called a process
- Threads are responsible for executing tasks: the execution of a program is ultimately performed within threads in the process
- Threads are managed by processes: Threads in a process cannot exist independently and are managed by processes
- Thread blocking: Threads in a process are blocked, and errors in any thread execution can cause the process to crash
- Data sharing: Data in a process is shared between threads
- Process isolation: Processes are isolated from each other and can communicate with each other through IPC
- Memory reclamation: When a process is running, you can manually or automatically reclaim the memory, or the operating system reclaims the memory after the process is stopped
When you open chrome’s Task Manager, you can see that every time you open a TAB, you start a new process.
Browsers are multi-process
The browser automatically starts a new process every time it opens a TAB page. What are these processes?
- The Browser process has only one main process (coordinating and controlling the Browser)
- The GPU process is used for 3D drawing
- Third party plugin process When you download a browser plugin from the Chrome Store and use it, a corresponding process is started
- Browser rendering process (browser kernel) default TAB open one, each other is not affected, mainly responsible for the control of page rendering, synchronous, asynchronous event processing
As shown in the figure:
The browser kernel is multithreaded
What threads are included in the browser renderer process (the browser kernel)?
It can be roughly divided into the following categories:
- The GUI thread
- Event trigger thread
- JS engine thread
- Timer thread
- Network request thread
So what does each thread do?
The GUI thread
- Responsible for rendering browser interface, parsing HTML, CSS, building DOM tree and Render tree, layout and rendering.
- When we change the size of the element, the page Reflow.
- When we change the color of some element or the background color, the page is repainted.
Note:
GUI rendering threads and JS engine threads are mutually exclusive
- GUI threads are suspended when the JS engine is executing.
- GUI updates are stored in a queue until the JS engine is idle and executed immediately
Event trigger thread
- Belongs to the browser kernel process and is not controlled by the JS engine thread. It is mainly used to control events (such as mouse, keyboard and other events). When the event is triggered, the event triggering thread will push the event processing function into the event queue and wait for the JS engine thread to execute
JS engine thread
- The JS engine thread is the JS kernel and is responsible for processing Javascript scripts (such as the V8 engine)
- The JS engine thread is responsible for parsing Javascript scripts and running code
- The JS engine waits for tasks to arrive in the task queue and then processes them
- Browsers can only have one JS engine thread running a JS program at a time, so JS is run single-threaded
- The GUI rendering thread is mutually exclusive with the JS engine thread, which blocks the GUI rendering thread
- We often encounter JS execution time is too long, resulting in incoherent page rendering, resulting in page rendering load blocking (is slow loading)
- For example, when the browser renders
Timer thread
- It mainly controls timer setInterval and delay setTimeout, which are used for timer timing. When the timer is finished and the trigger conditions of the timer are met, the timer processing function is pushed into the event queue and waits for the JS engine thread to execute.
Network request thread
- After the XMLHttpRequest connects, the browser opens a new thread request
- When a state change is detected, if a callback function is set, the asynchronous thread generates a state change event, which is then queued for execution by the JavaScript engine
- In simple terms, when an ASYNCHRONOUS HTTP request is executed, the asynchronous request event is added to the asynchronous request thread, and the callback function is added to the event queue, waiting for the JS engine thread to execute
Js is single threaded by nature
Why is JS single threaded?
As a browser scripting language, JavaScript’s primary purpose is to interact with users and manipulate the DOM. This means that it has to be single-threaded, which can cause complex synchronization problems. For example, if there are two threads of JavaScript at the same time, one thread adds content to a DOM node, and the other thread removes that node, which thread should the browser use? So, to avoid complexity, JavaScript has been single-threaded since its inception, and this has been a core feature of the language and will not change.
Event Loop
Synchronous tasks & Asynchronous tasks
We have just explained why JS is single-threaded, so single-threaded means that JS needs to wait in a queue when processing a task, and then execute the next task after the previous task is finished. If the first task takes a long time, the second task has to wait forever. Therefore, JS execution tasks can be divided into two types: synchronous tasks and asynchronous tasks.
- Synchronization task: A task that is queued to be executed on the main thread can be executed only after the previous task is completed.
- Asynchronous tasks: Tasks that do not enter the main thread but enter the Task queue will be executed only when the task queue notifies the main thread that an asynchronous task is ready to execute.
The execution mechanism of the two tasks is as follows:
- All synchronization tasks are executed on the main thread, forming an execution Context stack.
- In addition to the main thread, there is a “task queue”. Whenever an asynchronous task has a result, an event is placed in the “task queue”.
- Once all synchronization tasks in the execution stack are completed, the system reads the task queue to see what events are in it. Those corresponding asynchronous tasks then end the wait state, enter the execution stack, and start executing.
- The main thread repeats step 3 above.
When the main thread completes, it reads the “task queue” and repeats, which is the event loop.
Here’s a diagram to understand the event loop:
- When the overall script(as the first macro task) starts executing, all the code is divided into two parts: “synchronous task” and “asynchronous task”;
- The synchronization tasks are directly executed in the main thread.
- Asynchronous tasks are subdivided into macro tasks and micro tasks;
- The macro task enters the Event Table and registers the callback function in the Event Table. Each time the specified Event completes, the Event Table moves this function to the Event Queue.
- The microtask also goes to another Event Table and registers a callback function in it. When the specified Event completes, the Event Table moves this function to the Event Queue.
- When tasks in the main thread are completed and the main thread is empty, the Event Queue of microtasks will be checked. If there are tasks, they will all be executed. If there are no tasks, the next macro task will be executed.
- This process is repeated over and over again. This is the Event Loop;
Macro tasks & micro tasks
Macro task
It is understood that the code executed on each execution stack is a macro task (including fetching an event callback from the event queue and placing it on the execution stack each time).
To ensure orderly execution of JS internal (Macro task) and DOM tasks, the browser will re-render the page after the execution of one (Macro task) and before the execution of the next (Macro task).
(macro) task - > render - > (macro) task - >...Copy the code
What are common macro tasks?
PostMessage setImmediate(Node.js) setTimeout setInterval postMessage MediateCopy the code
Micro tasks
A microtask is a task that is executed immediately after the execution of the current task. That is, after the current task, before the next task, and before rendering.
So it responds faster than setTimeout (setTimeout is task) because there is no need to wait for rendering. That is, after a macroTask has been executed, all microTasks created during its execution have been executed (before rendering).
What are the common microtasks?
Promise.then object.observe process.nexttick (Node.js environment)Copy the code
What is the sequence between macro tasks and micro tasks?
When the macro task is executed, it will first determine whether there are microtasks, if there are microtasks, continue to execute the microtask, if there are no microtasks, directly browser rendering. This completes the first event loop and continues the next event loop to execute the next macro task. repeated
Soul searching interview questions
After sorting out the js event cycle mechanism above, we will masturbate several interview questions to deepen the impression.
The first question
console.log(1);
setTimeout(function(){
console.log(2)},0);
console.log(3)
Copy the code
Answer: 1,3,2
Console. log() is a synchronization task, setTimeout is an asynchronous task, and the asynchronous task will be executed after the synchronization task is completed. Although setTimeout is set to 0, the browser specifies a delay of at least 4ms, after which console.log(2) is put into the task queue. When the synchronization task is complete, print 1 and 3, and the main thread fetches the task from the task queue, printing 2.
The second question
console.log('A')
while(true) {}console.log('B')
Copy the code
The console.log is A synchronization task, which prints A first. When executing A while, it enters an infinite loop, so it is not executed later.
console.log('A');
setTimeout(function(){
console.log('B')},0);
while(1) {}Copy the code
When setTimeout is executed, it is an asynchronous macro task. It will be placed into the task queue, and continue to execute while to enter an infinite loop, so it will not print B
The third question
for(var i=0; i<4; i++){
setTimeout(function(){
console.log(i)
}, 0)}Copy the code
The order of execution is equivalent to
for(var i=0; i<4; i++){}
setTimeout(function(){
console.log(i)
}, 0)
setTimeout(function(){
console.log(i)
}, 0)
setTimeout(function(){
console.log(i)
}, 0)
setTimeout(function(){
console.log(i)
}, 0)
Copy the code
Answer: 4,4,4,4
The for loop is a synchronous task that executes first, and the value of I is 4. 4ms later, console.log(I) is placed in the task queue in turn. If there are no synchronization tasks in the execution stack, the tasks are removed from the task queue in turn, so four 4’s are printed.
So how do you print 0, 1,2,3 as expected? There are three methods:
Method 1: change var to let
for(let i=0; i<4; i++){
setTimeout(function(){
console.log(i)
}, 0)}// Method 2: use the immediate execution function
for(let i=0; i<4; i++){
(function(i){
setTimeout(function(){
console.log(i)
}, 0)
})(i)
}
// Add a closure
for(let i=0; i<4; i++){
var a = function(){
var j = i;
setTimeout(function(){
console.log(j)
}, 0)
}
a();
}
Copy the code
The fourth question
setTimeout(function(){
console.log(1)});new Promise(function(resolve){
console.log(2);
for(var i = 0; i < 10000; i++){
i == 9999 && resolve();
}
}).then(function(){
console.log(3)});console.log(4);
Copy the code
Answer: 2,4,3,1
Resolution:
- SetTimeout is asynchronous, and is a macro function, put in the macro function queue;
- New Promise is a synchronous task that executes directly, prints 2, and executes a for loop;
- Promise. then is the microtask, put in the microtask queue;
- Console. log(4) Synchronization task, execute directly, print 4;
- Then check that there is promise in the microtask queue. Execute the microtask and print 3.
- After the execution of the micro-task, the first cycle ends. Fetch the first macro task from the macro task queue to the main thread for execution, printing 1;
The fifth problem
console.log(1);
setTimeout(function() {
console.log(2);
}, 0);
Promise.resolve().then(function() {
console.log(3);
}).then(function() {
console.log('4. I'm a new microtask ');
});
console.log(5);
Copy the code
Answer: 1,5,3, ‘4. I’m a new microtask ‘, 2
Analysis:
- Console. log(1) is a synchronization task, executed directly, printing 1;
- SetTimeout is asynchronous, and is a macro function, put in the macro function queue;
- Promise.resolve(). Then is the microtask, put into the microtask queue;
- Console. log(5) is a synchronization task, executed directly, printing 5;
- Then, promise.resolve (). Then, execute the microtask, print 3;
- The second. Then task, which belongs to the microtask, is found, added to the microtask queue, and executed, printing 4. I’m the new microtask;
- During the execution of the microtask, if a new microtask is found, the new microtask will be added to the queue. The next cycle will be executed after the execution of the microtask queue is completed.
- After the execution of the micro-task, the first cycle ends. Set macro task setTimeout to the main thread, print 2;
The sixth question
function add(x, y) {
console.log(1)
setTimeout(function() { // timer1
console.log(2)},1000)
}
add();
setTimeout(function() { // timer2
console.log(3)})new Promise(function(resolve) {
console.log(4)
setTimeout(function() { // timer3
console.log(5)},100)
for(var i = 0; i < 100; i++) {
i == 99 && resolve()
}
}).then(function() {
setTimeout(function() { // timer4
console.log(6)},0)
console.log(7)})console.log(8)
Copy the code
Answer: 1,4,8,7,3,6,5,2
Analysis:
- Add () is a synchronization task, executed directly, printing 1;
- The setTimeout in add() is an asynchronous task and the macro function, called timer1, is placed on the macro queue;
- The setTimeout under add() is an asynchronous task and the macro function, called timer2, is placed on the macro queue;
- New Promise is a synchronous task that executes directly and prints 4;
- The setTimeout in Promise is an asynchronous task and the macro function, called timer3, is put on the macro function queue;
- The for loop in Promise, synchronizing tasks, executing code;
- Promise. Then is the microtask, put on the microtask queue;
- Console. log(8) is a synchronization task, executed directly, printing 8;
- Then check the microtask queue, and find that setTimeout is an asynchronous task and the macro function. Call it timer4 and put it in the macro function queue.
- Console. log(7) in the microtask queue is a synchronization task, executed directly, printing 7;
- After the execution of the micro-task, the first cycle ends.
- Check the macro task Event Table, which contains timer1, Timer2, timer3 and Timer4. The four timer macro tasks can be executed in the sequence according to the timer delay time, that is, the Event Queue: Timer2, Timer4, Timer3, timer1;
- Log (3) synchronizes with timer2.
- No microtask, the second Event Loop ends;
- Timer4, console.log(6);
- No microtask, the third Event Loop ends;
- Log (5) synchronization task, directly execute, print 5;
- No microtask, the fourth Event Loop ends;
- Timer1, console.log(2);
- No microtask, no macro task, the fifth Event Loop ends;
Number 7
setTimeout(function() { // timer1
console.log(1);
setTimeout(function() { // timer3
console.log(2); })},0);
setTimeout(function() { // timer2
console.log(3);
}, 0);
Copy the code
Answer: 1,3,2
Analysis:
- The first setTimeout is an asynchronous task and the macro function, called timer1, is put on the macro queue;
- The third setTimeout is an asynchronous task and the macro function, called timer2, is placed on the macro queue;
- No microtask, the first Event Loop ends;
- Timer1, console.log(1) synchronization task, directly execute, print 1;
- The setTimeout in timer1 is an asynchronous task and the macro function is called timer3 and placed in the macro function queue.
- No microtask, the second Event Loop ends;
- Timer2, console.log(3) synchronization task, directly execute, print 3;
- No microtask, the third Event Loop ends;
- Timer3, console.log(2) synchronization task, directly execute, print 2;
- No microtask, no macro task, the fourth Event Loop ends;
Event loops in Node
Node.js is an event-driven I/O server-side JavaScript environment based on Google’s V8 engine, which executes JavaScript very fast and performs very well. Libuv is a high-performance event-driven library written in C/C ++ as a cross-platform abstraction layer. Nodejs is similar to epoll in C/C ++ system programming
How Node.js works
- The V8 engine parses JavaScript scripts
- The parsed code calls the Node API
- The Libuv library is responsible for executing the Node API. It assigns different tasks to different threads, forming an Event Loop that asynchronously returns the execution results of the tasks to the V8 engine
- The V8 engine returns the results to the user
The Event Loop in Node is also quite different from the Event Loop in the browser.
The main process of Node.js is separated from the Event Loop. After the main process executes the synchronization code, the Event Loop does the rest.
Event loop in Node
Node event loops are executed in the following order:
Execute the whole code (synchronous macro task)–> execute the micro task (process.nexttick (), promise.then)–> Poll stage –> Check stage –> close event callback stage (close) Callback)–> Timer detection stage –>I/O event callback stage –> IDLE stage –> polling stage
Special process. NextTick
Process.nexttick is a very special microtask in Node.js, which usually starts a task queue separately and takes precedence over other microtasks. If both process.Nexttick and promise.then exist, the former will be executed first.
Promise.resolve().then(function() {
console.log('promise1')
})
process.nextTick(() = > {
console.log('nextTick')})// nextTick promise1
Copy the code
Watch out for setTimeout and setImmediate
SetTimeout and setImmediate mediate mediate mediate (FN, 0) do not perform certain actions without I/O processing. There is no guarantee that the timers will be able to execute the handlers immediately after entering the timers phase.
var fs = require('fs')
fs.readFile(__filename, () = > {
setTimeout(() = > {
console.log('timeout')},0)
setImmediate(() = > {
console.log('immediate')})})// immediate timeout
Copy the code
In the code above, setImmediate always executes first. The IO callback is performed in the POLL phase. The queue is empty after the POLL callback is completed. SetImmediate does not mediate, so it jumps directly to the Check phase to perform the callback.
What is the difference between the Event Loop in the browser and the Event Loop in Node
We can explain the difference by using an exercise on Node:
console.log('start')
setTimeout(() = > {
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')})},0)
setTimeout(() = > {
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')})},0)
Promise.resolve().then(function() {
console.log('promise3')})console.log('end')
// start end promise3 timer1 timer2 promise1 promise2
Copy the code
- From top to bottom, the synchronization task in the stack prints start and end, and the setTimeou macro task is placed in the task queue, and then the microtask prints promise3, just like the browser.
- When timers1 stage is executed, timers1 will be printed first, and then promise. Then will be put into the microtask queue. When timers1 stage is executed, Timers2 will be printed, which is quite different from the browser side. Timers several setTimeout/setInterval are executed in sequence in the Timers phase, unlike the browser side, which executes a microtask after each macro task.
conclusion
If you have any questions about this article, please leave them in the comments section.
If you are interested in the micro front end, you are welcome to read my previous post on building a micro front end architecture for your company from 0 to 1.