preface

In case you haven’t noticed, Vue executes asynchronously when updating the DOM. As long as it listens for data changes, Vue opens a queue and buffers all data changes that occur in the same event loop. If the same watcher is triggered more than once, it will only be pushed into the queue once. This removal of duplicate data while buffering is important to avoid unnecessary computation and DOM manipulation. Then, in the next event loop, “TICK,” Vue refreshes the queue and performs the actual (de-duplicated) work. Vue internally attempts to use native Promise.then, MutationObserver, and setImmediate for asynchronous queues, and setTimeout(fn, 0) instead if the execution environment does not support it.

Consider:

What to print first? 1. First, we need to understand the implementation mechanism of JS juejin.cn/post/684490… 2. Direct look at the source (SRC/core/observer/watcher. Js)

// 1. Data.set // 2. Dep.notify // 3. Update () {queueWatcher(this); } } const queue = []; function queueWatcher(watcher: Watcher) { // 5. Add the current Watcher to the asynchronous queue queue.push(Watcher); NextTick (flushSchedulerQueue); } function flushSchedulerQueue() {let watcher, id; Queue.sort ((a, b) => a.id-b.id); queue.sort((a, b) => a.id-b.id); // Run through all Watcher for batch update. for (index = 0; index watcher = queue[index]; // Update DOM watcher.run(); }}Copy the code

Based on the above code, we can derive a flow chart like this:

In this figure, Vue does not update the Watcher directly when it calls the Watcher update view. Instead, Vue adds the Watcher that needs to be updated to the Queue and then passes the specific update method flushSchedulerQueue to nextTick

Next, let’s analyze nextTick

const callbacks = []; let timerFunc; function nextTick(cb? : Function, ctx? : Object) { let _resolve; // 1. Add the passed flushSchedulerQueue method to the callback array callbacks.push(() => {cb.call(CTX); }); TimerFunc (); timerFunc(); timerFunc(); }Copy the code

As you can see, the nextTick function is very simple; it simply adds the flushSchedulerQueue passed in to the Callbacks array and then executes the timerFunc method.

Next, let’s examine the timerFunc method.

let timerFunc; // Check whether compatible Promise if (typeof Promise! == "undefined") { timerFunc = () => { Promise.resolve().then(flushCallbacks); }; / / determine whether compatible MutationObserver / / https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver} else if (typeof MutationObserver ! == "undefined") { let counter = 1; const observer = new MutationObserver(flushCallbacks); const textNode = document.createTextNode(String(counter)); observer.observe(textNode, { characterData: true, }); timerFunc = () => { counter = (counter + 1) % 2; textNode.data = String(counter); }; } Else if (typeof setImmediate! TimerFunc = () => {setImmediate(flushCallbacks); }; } else {// If none of the above methods know, use setTimeout 0 timerFunc = () => {setTimeout(flushCallbacks, 0); }; FlushSchedulerQueue function flushCallbacks() {for (let I = 0; i callbacks[i](); }}Copy the code

As you can see, timerFunc is an asynchronous method created for browser compatibility, and when it completes, it calls the flushSchedulerQueue method for a specific DOM update. Analysis here, we can get an overall flow chart.

Next, let’s refine some judgment logic.

  • has

Is an object that filters watcher. When the watcher has already called the update function, mark the id in has. That is, if you call watcher.update multiple times at the same time, only the first call is useful, and all subsequent calls are filtered out

  • waiting

True indicates that the Watcher update queue execution function has been registered with the macro/micro task (or stored in the Callbacks). You are waiting for the JS stack to be empty before performing the update. Reset to false until all watcher updates are complete

  • flushing

A value of true indicates that the Watcher update queue is performing updates until all watcher updates are completed

Combined with the above judgment, the final flow chart is as follows

Finally, why does this.$nextTick get the updated DOM?

Prototype.$nextTick = function (fn: Function) { return nextTick(fn, this); };Copy the code

As you can see, calling this. nextTick actually calls the nextTick method in the figure, executing the callback function in the asynchronous queue. According to the principle of first come first served, the update asynchronous queue triggered by Data modification will be executed first, and a new DOM will be generated after the execution is completed. The next execution of this.nextTick is actually to call the nextTick method in the figure and execute the callback function in the asynchronous queue. According to the principle of first come first served, the update asynchronous queue triggered by Data modification will be executed first, and a new DOM will be generated after the execution is completed. The next execution of this.nextTick is actually to call the nextTick method in the figure and execute the callback function in the asynchronous queue. According to the first-come-first-served principle, the asynchronous update queue triggered by Data modification will be executed first, and a new DOM will be generated after execution. Then, when the this.nextTick callback is executed, the updated DOM element will be retrieved. $nextTick: this.$nextTick: this.$nextTick: this.

Thinking and summarizing

  1. When you modify the Data in the Vue, it triggers all Watcher updates associated with that Data.
  2. First, all the watchers are queued.
  3. The nextTick method is then invoked to perform the asynchronous task.
  4. In the callback of the asynchronous task, the Watcher in the Queue is sorted and the corresponding DOM update is performed.

Extension:

So how do you implement a promise before nextTick? (The code order in the problem remains the same)

NextTick supports either a callback or a promise, depending on whether or not you pass in a callback function

export function nextTick (cb? : Function, ctx? : Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (! pending) { pending = true timerFunc() } // $flow-disable-line if (! cb && typeof Promise ! == 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }Copy the code

This.$nextTick().then(…)

And this last one is pretty clear to show that you understand it pretty much

mounted() { this.$nextTick().then(()=>{ console.log('nextTick'); }) Promise.resolve().then(()=>{ console.log('promise111'); }).then(()=>{ console.log('promise2222'); })}Copy the code