Vue nextTick principle

Vue. Js uses an asynchronous update strategy for view updates. Let’s take a look at how it works.

/ * *? */ for(let i = 0; i < 100; i++) { this.count++; } / * *? This.$nextTick(fn)Copy the code

Two questions:

  1. Will the DOM be updated 100 times as the for loop updates the count value?
  2. How does nextTick monitor the completion of DOM updates?

Asynchronous update involves js operation mechanism, detailed can see here [Event Loop mechanism] this article we mainly from the source Angle to analyze the principle of nextTick implementation.

This is the Watcher class in our response

<! --> class Watcher {constructor () {constructor. Target = this // new Watcher; update () { } run () {// dom performs real updates here}}Copy the code

The watcher object is being updated and the update is performed internally by a queueWatcher function that passes the watcher object as this, so we start with queueWatcher.

queueWatcher

The queueWatcher function is in the scheduler file

/** queueWatcher function */ let has = {}; let queue = []; let waiting = false; function queueWatcher (watcher: If (has[id] == null) {has[id] = true queue.push(Watcher) // Pass the update task if (! Waiting) {waiting = true nextTick(flushSchedulerQueue)}}} /** flushSchedulerQueue */ function flushSchedulerQueue () { let watcher, id; for (index = 0; index < queue.length; index++) { watcher = queue[index]; id = watcher.id; has[id] = null; // Execute the update watcher.run(); } // Waiting = false; }Copy the code
  1. The queue contains the watcher that we want to update. The queueWatcher function performs a redo operation, and the same watcher will only be added to the queue once.
  2. The flushSchedulerQueue function in turn calls the run method of the Wacther object to perform the update. And passed as a callback to the nextTick function.
  3. Waiting indicates whether or not the update task has been passed to nextTick. NextTick will handle the callback after the current task is finished. Waiting indicates whether or not the update task has been passed to nextTick.

next-tick

let callbacks = []; let pending = false; let timerFunc; /**----- nextTick -----*/ function nextTick (cb) {callbacks. Push (cb); // pending represents a waiting state for the tick to execute if (! Pending) {pending = true timerFunc()} // Provide a promiser call if (! cb && typeof Promise ! == 'undefined') {return new Promise(resolve => {_resolve = resolve})}} /**----- timerFunc ----*/ //  if (typeof Promise ! == 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) if (isIOS)  setTimeout(noop) } isUsingMicroTask = true } else if (! isIE && typeof MutationObserver ! == 'undefined' && ( isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor] ')) {/ / 2 = 1, relegated to MutationObserver let counter 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) } isUsingMicroTask = true } else  if (typeof setImmediate ! = = 'undefined && isNative (setImmediate)) {/ / 3, relegated to setImmediate timerFunc = () = > {setImmediate (flushCallbacks)}} TimerFunc = () => {setTimeout(flushCallbacks, flushCallbacks); }} function flushCallbacks () {const copies = false const copies = callbacks. Slice (0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }Copy the code
  1. Callbacks are passed in and stored in the Callbacks queue. The callbacks are used instead of executing the callback function directly in the nextTick, because this ensures that nextTick can be executed multiple times in the same tick, rendering in one tick, and that multiple asynchronous tasks are not enabled.

    Function nextTick(cb) {setTimeout(cb)} nextTick(cb1) nextTick(cb2) In this case, two asynchronous tasks are started, or two event loops, causing the page to render unnecessarilyCopy the code
  2. TimerFunc is at the heart of the implementation. It prioritizes microtasks such as Promises that are executed within the same event loop so that pages only need to be rendered once. Using setTimeout will cause a second render, but this is the worst case scenario. Vue uses the strategy of degrading here.

$nextTick

Finally, hang the nexttick function on the Vue prototype

Vue.prototype.$nextTick = function (fn) {
    return nextTick(fn, this)
}
Copy the code

summary

Vue asynchronous update, in essence, is an application of JS event mechanism, which gives priority to microtasks with high priority, and makes a degradation strategy for compatibility.

Now back to the first two questions

  1. Will the DOM be updated 100 times as the for loop updates the count value?

    No, because the queueWatcher function does the filtering, the same Watcher object will not be added repeatedly.

  2. How does nextTick monitor the completion of DOM updates?

    Vue uses an asynchronous queue to control the DOM update and nextTick callback, ensuring that the callback can be executed after the DOM update.