Last illustrated Vue responsive principle, we through 9 flow charts, understand the Vue rendering process, I believe we have a certain understanding of the whole Vue rendering process, this article we will focus on Vue asynchronous update principle module.

This article focuses on the flow of Vue from Data updates to notifting Watcher to update the view asynchronously, which is shown in orange in the figure below.

Let’s start by reviewing a few objects in the image below:

  • Data object: An object returned from the Data method in Vue.
  • Dep object: Each Data property creates a Dep that collects all Watcher objects that use this Data.
  • Watcher object: Mainly used to render the DOM.

Next, let’s start analyzing the process.

Vue asynchronously updates DOM

As many of you know, Data updates in Vue are asynchronous, which means that we cannot immediately get the modified DOM element after modifying Data.

Such as:

<template> <div> <span id="text">{{ message }}</span> <button @click="changeData"> changeData </button> </div> </template> <script> export default { data() { return { message: "hello", }; }, methods: { changeData() { this.message = "hello world"; const textContent = document.getElementById("text").textContent; Console. log(textContent === "Hello world"); $nextTick() => {const textContent = document.getelementById ("text").textContent; console.warn(textContent === "hello world"); // true }); ,}}}; </script>Copy the code

When do we get the actual DOM element?

A: In Vue’s nextTick callback.

This is explained in detail on the Vue website, but have you ever wondered why Vue needs the nextTick method to get the latest DOM?

With that in mind, let’s go straight to the source code.

// When a Data is updated, the following code is executed in sequence
// 1. Trigger data.set
// 2. Call dep.notify
// 3. Dep iterates through all relevant Watcher to execute the update method
class Watcher {
  // 4. Perform the update
  update() {
    queueWatcher(this); }}const queue = [];

function queueWatcher(watcher: Watcher) {
  // 5. Add the current Watcher to the asynchronous queue
  queue.push(watcher);
  // 6. Execute the asynchronous queue and pass in the callback
  nextTick(flushSchedulerQueue);
}

// How to update the view
function flushSchedulerQueue() {
  let watcher, id;
  // Sort, first render the parent node, then render the child node
  // This avoids unnecessary rendering of children, such as children of the parent whose V-if is false
  queue.sort((a, b) = > a.id - b.id);
  // Run through all Watcher for batch update.
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index];
    / / update the DOMwatcher.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);
  });
  // 2. Execute an asynchronous task
  // This method uses a different asynchronous strategy depending on browser compatibility
  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;
// Determine compatibility with Promise
if (typeof Promise! = ="undefined") {
  timerFunc = () = > {
    Promise.resolve().then(flushCallbacks);
  };
  // Determine compatibility with MutationObserver
  // https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
} else if (typeofMutationObserver ! = ="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);
  };
  // Determine whether setImmediate is compatible
  // This method exists in some IE browsers
} else if (typeofsetImmediate ! = ="undefined") {
  // This is a macro task, but better than setTimeout
  timerFunc = () = > {
    setImmediate(flushCallbacks);
  };
} else {
  If none of the above methods are known, use setTimeout 0
  timerFunc = () = > {
    setTimeout(flushCallbacks, 0);
  };
}

FlushSchedulerQueue (flushSchedulerQueue) {flushSchedulerQueue (flushSchedulerQueue)
function flushCallbacks() {
  for (let i = 0; i < copies.length; 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.

  • Determine the HAS flag to avoid adding the same Watcher to a Queue.
  • Check waiting and make all Watcher updates in one tick.
  • Judge the flushing flag, and when you process the Watcher rendering, you might generate a new Watcher.
    • For example: triggered v-if condition, new Watcher rendering.

Based on the above judgment, the final flow chart is as follows:

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

// We use this.$nextTick to call the nextTick method
Vue.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. This.$nextTick callback is used to retrieve the updated DOM element. This.$nextTick callback is used to retrieve the DOM element.

Since nextTick is simply an asynchronous task emulated with methods like Promise, SetTimeout, etc., it is possible to execute an asynchronous task manually to achieve the same effect as this.$nextTick.

this.message = "hello world";
// It is also possible to retrieve the latest DOM by manually executing an asynchronous task
Promise.resolve().then(() = > {
  const textContent = document.getElementById("text").textContent;
  console.log(textContent === "hello world"); // true
});
setTimeout(() = > {
  const textContent = document.getElementById("text").textContent;
  console.log(textContent === "hello world"); // true
});
Copy the code

Thinking and summarizing

This article introduces the principle of Vue asynchronous update from the source point of view, to a brief review.

  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.

Finally, if you have any thoughts on this, feel free to leave a comment!