“Stick to this and you beat 99 percent of your opponents” \
Macro task
Most of the tasks in the page are performed on the main thread. These tasks include:
Render events (such as PARSING DOM, calculating layout, drawing); User interaction events (such as mouse click, page scrolling, zooming in and out, etc.); JavaScript script execution events; Network request completed, file read/write completed event.
To coordinate the execution of these tasks methodically on the main thread, the page process introduces message queues and event loops, and the renderer process maintains multiple message queues internally, such as deferred execution queues and regular message queues. The main thread then takes a for loop, continually fetching and executing tasks from these task queues. We call these tasks in the message queue macro tasks.
Macro tasks can meet most of our daily needs, but if there is a high demand for time precision, macro tasks are not competent. Let’s analyze why macro tasks are difficult to meet the high demand for time precision tasks.
As mentioned earlier, page rendering events, various IO completion events, JavaScript script execution events, user interaction events, etc., can be added to the message queue at any time. Moreover, the adding events are operated by the system, and the JavaScript code cannot accurately control where tasks are added to the queue. There is no control over the position of the task in the message queue, so it is difficult to control when the task starts. To get an idea, you can look at the following code:
<! DOCTYPE html> <html> <body> <div id='demo'> <ol> <li>test</li> </ol> </div> </body> <script type="text/javascript"> Function timerCallback2(){console.log(2)} function timerCallback(){console.log(1) setTimeout(timerCallback2,0)} setTimeout(timerCallback,0) </script> </html>Copy the code
In this code, I want to use setTimeout to set the two callback tasks, and let them be executed in sequence, and do not insert any other tasks in between, because if the two tasks are inserted in between, it is likely to affect the execution time of the second timer. However, the reality is that we can’t control it. For example, when you call setTimeout to set the interval of callback tasks, many system-level tasks can be inserted into the message queue. You can open the Performance tool to record the execution of this task, or refer to the pictures I recorded in the article:
All callbacks triggered by setTimeout function are macro tasks. In the figure, the two yellow blocks on the left and right are the two timer tasks triggered by setTimeout. Now you can focus on the light red area in the middle of the image, where there are a number of tasks that the rendering engine has inserted between two timer tasks. Just think about it, if the task that is inserted in the middle takes too long to execute, it will affect the execution of the later tasks. Therefore, the time granularity of macro tasks is relatively large, and the time interval of execution cannot be precisely controlled, which does not meet some high real-time requirements, such as the requirement of monitoring DOM changes to be introduced later.
Micro tasks
Now that we understand macro tasks, let’s take a look at what microtasks are. We introduced the concept of asynchronous callbacks in two main ways. The first is to encapsulate the asynchronous callback function as a macro task, add it to the end of the message queue, and execute the callback function when the loop executes the task. This is easy to understand, as the setTimeout and XMLHttpRequest callbacks we described earlier are implemented this way. The second method is executed after the main function completes but before the current macro task completes, usually in the form of a microtask. So what exactly is microtask? A microtask is a function that needs to be executed asynchronously, after the completion of the main function but before the completion of the current macro task. But to understand how the micromission system works, you have to look at the V8 engine. We know that when JavaScript executes a script, V8 creates a global execution context for it, and at the same time that the global execution context is created, V8 also creates a queue of microtasks internally. As the name implies, this microtask queue is used to store microtasks, because in the current macro task execution process, sometimes there are multiple microtasks, this microtask queue is needed to store these microtasks. However, the microtask queue is for internal use in the V8 engine, so you can’t access it directly through JavaScript. That is, each macro task is associated with a microtask queue. Then, we need to analyze two important points in time — when the microtask is generated and when the microtask queue is executed. Let’s start by looking at how microtasks come about, okay? In modern browsers, there are two ways to generate microtasks. The first approach is to use MutationObserver to monitor a DOM node, and then modify the node through JavaScript, or add or remove partial nodes to the node. When the DOM node changes, the micro-task of recording the DOM changes is generated. The second approach is to use promises, which also produce microtasks when promise.resolve () or promise.reject () is called. Microtasks generated by DOM node changes or by using promises are sequentially stored in the microtask queue by the JavaScript engine. Ok, now that we have the microtask in the microtask queue, it’s time to look at when the microtask queue is executed. In general, when the JavaScript in the current macro task is about to complete, just as the JavaScript engine is about to exit the global execution context and clear the call stack, the JavaScript engine will check the queue of microtasks in the global execution context, The microtasks in the queue are then executed in sequence. The WHATWG refers to the point at which a microtask is performed as a checkpoint. There are other checkpoints besides exiting the global execution context, but they are not so important that they will not be covered here. If a new microtask is created during the execution of a microtask, it is added to the microtask queue. The V8 engine executes the tasks in the microtask queue repeatedly until the queue is empty. That is, new microtasks generated during the execution of a microtask are not postponed to the next macro task, but continue to be executed in the current macro task. To get a sense of what a microtask is, you can refer to the diagram I drew below (I divided it into two because of the large content) :
This schematic diagram is in the execution of a ParseHTML macro task, in the execution process, encountered JavaScript script, then pause the parsing process, into the execution of JavaScript environment. As you can see from the figure, the global context contains a list of microtasks. In subsequent execution of the JavaScript script, two microtasks were created using Promise and removeChild, respectively, and added to the list of microtasks. Then the JavaScript finishes executing and it’s ready to exit the global execution context, at which point the checkpoint is reached. The JavaScript engine checks the list of microtasks, finds that there are microtasks in the list, and then executes these two microtasks in turn. After the microtask queue is empty, the global execution context is exited.
The above is the working process of microtask. From the analysis above, we can draw the following conclusions:
Microtasks and macro tasks are bound, and each macro task creates its own microtask queue when it executes.
The duration of a microtask affects the duration of the current macro task. For example, during the execution of a macro task, 100 microtasks are generated, and the execution time of each microtask is 10 milliseconds, then the execution time of 100 microtasks is 1000 milliseconds, or it can be said that the 100 microtasks increase the execution time of the macro task by 1000 milliseconds. So when you’re writing code, it’s important to control the duration of microtasks.
In a macro task, create a macro task and a microtask for callbacks, and in either case, the microtask precedes the macro task.