Need to reserve pre-knowledge: Event Loop, macro task, micro task.

Event Loop

Synchronous and Asynchronous

As we all know, one of the characteristics of JS is that JS is single-threaded, which can be understood as that only one thing can be done at a time. Since the main purpose of JS is to interact with the user, manipulate the DOM, etc., it can only be single-threaded, so why? If js is not single-threaded but multi-threaded, which thread should the browser use if one thread adds content to a DOM node and another thread deletes the node? So js is designed to be single threaded, which makes it easier for us to operate.

However, single threading means that all tasks are queued up, and the next task is executed only after the previous one has finished. If the previous task takes a long time, the later task will just have to wait. We know that computing is the use of the CPU and input/output is IO, when we use the ajax request data, we wait for the result to perform again, in this waiting time of the CPU is idle, so designers realized that this period of time the main thread can regardless of IO, hang in waiting for the task, to run the first row in the back of the task. Wait until the IO returns the result, then go back and continue the suspended task. Thus, all tasks can be divided into two types, one is synchronous task and the other is asynchronous task. A synchronous task is a task that is queued for execution on the main thread. A subsequent task can be executed only after the previous task is completed. Asynchronous tasks do not enter the main thread, but are placed in a task queue.

Task queue

A task queue is a queue of events, also known as a queue of messages. It is a first-in, first-out data structure. The first events in the queue are read first by the main thread.

The mechanism of asynchronous execution is:

  1. All synchronization tasks are executed on the main thread, forming an execution stack.
  2. In addition to the main thread, there is a task queue into which the asynchronous task is not entered, and an event is placed in the task queue whenever the asynchronous task has run results.
  3. Once all synchronization tasks in the execution stack have been executed (that is, the main thread is empty), the system reads the task queue to see what events are in it. The corresponding asynchronous tasks then end the wait state, enter the execution stack, and start to execute.
  4. The main thread repeats the previous step

The main thread reads events from the task queue in a continuous Loop, so the whole mechanism is called an Event Loop.

Macro task

Macro tasks represent discrete, independent units of work. The browser completes one macro task and rerenders the page before the next macro task begins. This includes creating the main document object, parsing HTML, executing the main JS code, and various events such as page loading, input, network events, and timers.

Macro tasks are executed in order, and the browser renders the page between each macro task. In other words, in order to enable the internal JS task and DOM tasks to be executed in order, the browser will execute after the completion of one task and before the next task. Re-render the page (task-> Render -> Task ->…) .


Microtask: A microtask is a smaller task that is executed immediately after the execution of the current macro task. If microtasks exist, the browser will clean up the microtasks before rerendering. Common microtasks include promise, process. NextTick, MutationObserver, etc.

All microtasks are also executed in sequence, and all microtasks are executed immediately in the following scenarios:

  1. After each callback and the JS execution stack is empty.
  2. After each macro task is completed.


We know that setTimeout is a timer that specifies how long some code or function will be executed after, taking two arguments, the first is the callback function, and the second is the number of milliseconds to delay execution. However, setting the second argument of setTimeout() to 0 means that the specified callback function will be executed (0 milliseconds interval) immediately after the current code finishes executing (the execution stack is cleared).

SetTimeout (fn,0) specifies that a task should be executed at the earliest available free time on the main thread, that is, as early as possible. It adds an event to the end of the task queue, so it will not be executed until the synchronization task and the existing events of the task queue have been processed.

Vue concrete implementation

  1. Asynchronous: As long as data changes are listened for, Vue will start a queue and buffer all data changes that occur in the same event loop.
  2. Batch: If the same watcher is triggered multiple times, it will be pushed to the queue only once. Deweighting is important to avoid unnecessary computation and DOM manipulation. Then, in the next event loop, tick, Vue refreshes the queue to do the actual work.
  3. Asynchronous strategy: Vue internally tries to use native Promise. Then, MutationObserver, and setImmediate for asynchronous queues. If none of the execution environments supports it, setTimeout(fn, 0) is used instead.

So let’s take a look at how this works in the source code.

So in the responder, we know that we’re modifying the data, we’re doing the setter, and the core of that is calling the notify method of DEp.


In dep’s notify method, we see that we take all the watcher and iterate over the update method that executes each watcher in turn.


In the watcher update method, I can see that there are several judgments, such as lazy and sync, which we don’t usually set, so the core is to call queueWatcher -> watcher to join the queue.


If the watcher is not in the queue and is not refreshing, then the watcher is queued.

It then determines if it is in a wait state, and if not, the task queue is executed asynchronously.

FlushSchedulerQueue is the exact method of execution (get watcher, call run), and nextTick is the asynchronous method of flushing. So let’s track down nextTick.


We define an empty callbacks array at the top of the file, and we put the wrapped function into the array. For example, to flushSchedulerQueue, run the following command: It then determines if it is not suspended and executes the timerFunc function.

On top of this method, we define the timerFunc function.

So we see that the preferred asynchronous solution is Promise and executes the callbacks in a microtask fashion. If promise is not supported, then determine whether to support MutationObserver; if neither, then setImmediate; if neither, then setTimeout(flushCallbacks, 0) is used.

Let’s see what flushCallbacks are.

FlushSchedulerQueue: flushSchedulerQueue