Vue source code interpretation series

  • 1. Vue responsive Principles – Understand Observer, Dep, Watcher
  • 2. Vue responsive Principle – How to listen for Array changes
  • 3. Vue responsive Principle – How to listen for Array changes? A detailed version
  • 4. Vue asynchronous update && nextTick source parsing

1. Vue asynchronous update queue

(1) Vue asynchronous update

As you all know, Vue can do data-driven view update. For example, let’s simply write an event as follows:

methods: {
    tap() {
        for (let i = 0; i < 10; i++) {
            this.a = i;
        }
        this.b = Awesome!; }},Copy the code

When we fire this event, a and B in the view will definitely see some changes.

So let’s think about, how does Vue manage this change process? For example, in the case above where A is looping 10 times, will Vue render the view 10 times? Obviously not, since the performance cost is very high. After all, we only need the last assignment of A.

In fact, Vue updates the view asynchronously, that is to say, it will wait until the tap event is finished, and then check that only A and B need to be updated, and then update once again to avoid invalid updates.

Vue official documents also confirm our ideas, as follows:

Vue executes asynchronously when updating the DOM. As soon as data changes are listened for, Vue opens a queue and buffers all data changes that occur in the same event loop. If the same watcher is fired multiple times, it will only be pushed into the queue once. This removal of duplicate data at buffering time is important to avoid unnecessary computation and DOM manipulation.

The above can be seen in Vue official document – Asynchronous Update Queue.

(2) Asynchronous queue in update distribution

Vue notifies view updates via dep.notify. You are sure to understand Vue responsiveness by reading this. So what does dep.notify do? Be patient. We’re getting closer to the truth.

// dep.js
notify () {
    const subs = this.subs.slice();
    // Loop to notify all watcher of updates
    for (let i = 0, l = subs.length; i < l; i++) {
        subs[i].update()
    }
}
Copy the code

First, the loop notifies all watcher of the update, and we find that watcher executes the update method.


// watcher.js
update () {
    if (this.lazy) {
        // If the attribute is calculated
        this.dirty = true
    } else if (this.sync) {
        // If you want to synchronize the update
        this.run()
    } else {
        // Enter the update queue
        queueWatcher(this)}}Copy the code

The update method first determines whether the property is being evaluated or whether the developer has defined a synchronous update, so let’s skip that and go straight to queueWatcher.

So let’s look at queueWatcher, and I’m going to leave out most of the code, because it’s boring, but just for the sake of understanding, it’s just thinking code.

export function queueWatcher (watcher: Watcher) {
    / / get watcherid
    const id = watcher.id
    if (has[id] == null) {
        // Make sure there is only one watcher to avoid duplication
        has[id] = true
        
        // Push into the queue to be executed
        queue.push(watcher)
      
        / /... Omit detail code
    }
    // Put all update actions into nextTick and push them to the asynchronous queue
    nextTick(flushSchedulerQueue)
}

function flushSchedulerQueue () {
    for (index = 0; index < queue.length; index++) {
        watcher = queue[index]
        watcher.run()
        / /... Omit detail code}}Copy the code

From the above code you can see that we put all the watcher queues to be updated into the nextTick. NextTick is officially read as:

The deferred callback is performed after the next DOM update loop ends. Use this method immediately after modifying the data to get the updated DOM.

The description here actually limits the skill of nextTick, which is actually an asynchronous method, probably not much different from the setTimeout you use.

So what does the nextTick source do?

Second, nextTick source analysis

The nextTick source code is sparse, with only a few lines to go through, but I’m not going to go into it because it’s really boring. The following code is only a few lines, but you can skip it to see the conclusion.

// timerFunc is the callback passed by nextTick... The details are not expanded
if (typeof Promise! = ='undefined' && isNative(Promise)) {
    const p = Promise.resolve()
    timerFunc = (a)= > {
        p.then(flushCallbacks)
    }
    isUsingMicroTask = true
} else if(! isIE &&typeofMutationObserver ! = ='undefined' && (
    // timerFunc uses a native MutationObserver when a native Promise is unavailable
    // MutationObserver Does not care about its functionality. It is a spare wheel that can achieve microtask effects
)) {
    timerFunc = (a)= > {
        / / use MutationObserver
    }
    isUsingMicroTask = true

} else if (typeofsetImmediate ! = ='undefined' &&  isNative(setImmediate)) {
    // If native setImmediate is available, timerFunc uses native setImmediate
    timerFunc = (a)= > {
        setImmediate(flushCallbacks)
    }
} else {
    // For the last stubborn, timerFunc uses setTimeout
    timerFunc = (a)= > {
        setTimeout(flushCallbacks, 0)}}Copy the code

Promise > MutationObserver > setImmediate > setTimeout.

It’s not that different from setTimeout

To summarize priorities again: MicroTask (Jobs) takes precedence.

Why does nextTick source code want microTask first? Before we can understand the answer to this question, we need to review eventLoop.

Third, eventLoop

(1) Task queue

I’m going to give you a quick overview, but I’m not going to go into detail, so you can look it up.

  • Our synchronization tasks running on the main thread form an execution stack.
  • If you have an asynchronous task, for examplesetTimeout,onClickAnd so on, we will queue the result of its execution, during which the main thread does not block.
  • When all synchronization tasks in the main thread have been executed, it will passevent loopIt is fetched from scratch in the queue and executed in the execution stackevent loopIt will never break.
  • The whole process above isEvent Loop(Event loop).

(2) Microtask and macro task

  • Each time the synchronous task of the execution stack is completed, the asynchronous task will be retrieved from the task queue, but the queue is divided into microtasksmicrotaskAnd the macro tasktasksThe queue
  • Wait until all the microtasksmicrotaskIt’s all done. Note yesAll of the, he will be from the macro tasktasksFetch events from the queue.
  • When one of the events in the queue is taken out and put into the execution stack, the execution is complete.
  • afterevent loopIt’s going to continue the loop, he’s going to microtask againmicrotaskExecute all the tasks and then from the macro tasktasksTake one out of the queue, and so on and so forth.

Why should nextTick microtask first as much as possible?

After a brief recall of eventLoop, microtask, and macro task, we have one more conclusion to throw out.

We found out that rendering is performed after performing microtasks!! (Not every time, of course, but at least we can be sure of the order).

  • In the roundevent loopIs modified multiple times indomOnly the last time will be drawn.
  • Render update (Update the rendering) in theevent loopIn thetasksandmicrotasksDo it after completion, but not every roundevent loopAll renderings will be updated, depending on whether it’s changed or notdomAnd whether the browser feels the need to immediately present the new state to the user at this point. If you make multiple changes in the time of one frame (the time is uncertain, because the browser’s frames per second always fluctuate, 16.7ms is just an inaccurate estimate)dom, the browser might hoard the changes and only draw once, which makes sense.
  • If hope in each roundevent loopAll of them are changing in real time and can be usedrequestAnimationFrame.

Here I throw out the conclusion, the reason and theoretical knowledge can be seen in this article from the Event Loop specification to explore javaScript asynchronous and browser update rendering timing, the great god wrote very well.

Why should nextTick prioritize microtasks as much as possible? Here is the rough loop process of the event loop:

Let’s say we are now executing to a task, and we are making asynchronous changes to the DOM of the batch. We are inserting this task into the tasks, that is, the macro task implementation.

Obviously, in this case, if there are too many queues in the task, the microtask queue will be executed multiple times at the same time. That probably triggered multiple browser renderings, but still didn’t perform our actual DOM modification task.

This not only delays view updates, but also causes performance problems. It can also cause some weird problems with the view. Therefore, this task inserts microTasks:

task
dom
microtasks
task

Refer to the article

Some of the conclusions of this article refer directly to other articles, but I am too lazy to write them

  • Node Base interview event loop? Microtask, macro task? One takes you flying
  • Explore javaScript asynchrony and browser update rendering timing from event Loop specification

Infringement delete ^ ^