Comprehensive analysis of the implementation principle of Vue.nextTick

There is a special API in vue, nextTick. According to the explanation of the official documentation, it can execute a callback after the DOM is updated. The usage is as follows:

// change the data

 

vm.msg 'Hello'

 

// DOM has not been updated

 

Vue.nextTick(function () {

 

// DOM updated

 

})

Although the MVVM framework does not recommend accessing the DOM, sometimes there is such a need, especially when working with third-party plug-ins, DOM manipulation is inevitable. And nextTick provides a bridge to ensure that we are operating on the updated DOM.

This article starts with the question: How does Vue detect that the DOM has been updated?

Retrieve your own front-end knowledge base, the only API that can monitor DOM changes seems to be MutationObserver, hereinafter referred to as MO.

Understanding MutationObserver

MutationObserver is a new attribute in HTML5. It is used to monitor DOM modification events. It can monitor changes in node attributes, text content, and child nodes. It is a powerful tool. The basic usage is as follows:

//MO basic usage

 

var observer new MutationObserver(function(){

 

  // here is the callback function

 

  console.log ( ' DOM has been modified!' );

 

});

 

var article = document.querySelector('article');

 

observer.observer(article);

The use of MO is not the focus of this article. What we need to think about here is: Does Vue use MO to monitor the completion of the DOM update?

Then open the source code of vue and take a look. In the place where nextTick is implemented, you can indeed see such code:

//[email protected] /src/core/util/env.js

 

if (typeof MutationObserver !== 'undefined' && (isNative(MutationObserver|| MutationObserver.toString() === '[object MutationObserverConstructor]')) {

 

  var counter  1

 

  var observer new MutationObserver(nextTickHandler)

 

  var textNode = document.createTextNode(String(counter))

 

  observer.observe(textNode{

 

      characterDatatrue

 

  })

 

  timerFunc () => {

 

    counter (counter 12

 

    textNode.data String(counter)

 

  }

 

}

To briefly explain, if it is detected that the browser supports MO, a text node is created, and the change event of this text node is monitored, so as to trigger the execution of nextTickHandler (that is, the DOM update callback). In the following code, manual modification of the text node properties will be performed, so that the callback function can be entered.

After a general glance, it seems that I can get a real hammer: Oh! Vue uses MutationObserver to monitor DOM updates!

Don't you feel something is wrong? Let's think about it for a second:

  1. What we want to monitor is that the DOM in the template is updated. Why does Vue create a text node to monitor it? This is a bit unreasonable!

     

  2. Could it be that if the text node created by oneself is updated, can it represent the update of other DOM nodes? What's the point of this!

It seems that the conclusions we have drawn above are not correct. At this time, we need to talk about the event loop mechanism of js.

Event Loop

In the js running environment, let's just talk about the browser here, which is usually accompanied by many events, such as user clicks, page rendering, script execution, network requests, and so on. To coordinate the processing of these events, browsers use an event loop mechanism.

Briefly, the event loop maintains one or more task queues, and the above-mentioned events are used as task sources to add tasks to the queues. There is a continuous thread to process these tasks, and each time one is executed, it is removed from the queue, which is an event loop, as shown in the following figure:

We usually use setTimeout to execute asynchronous code. In fact, we add a task to the end of the task queue, and execute it after the previous tasks have been executed.

The key point is here. At the end of each event loop, there will be a UI render step, which is to update the DOM. Why is the standard designed this way? Consider the following code:

for(let i=0; i<100; i++){

 

    dom.style.left = i 'px';

 

}

Do browsers do 100 DOM updates? Obviously not, this is too performance-intensive. In fact, these 100 for loops belong to the same task, and the browser only performs one DOM update after the task is executed.

Then our idea comes: as long as the code in nextTick is executed after the UI render step, wouldn't it be possible to access the updated DOM?

Vue is such a way of thinking. It does not use MO to monitor DOM changes, but uses queue control to achieve the goal. So how does Vue achieve queue control? We can naturally think of setTimeout, and put the code to be executed by nextTick as the next task and put it at the end of the queue.

However, things are not so simple. Vue's data response process includes: data change -> notify Watcher -> update DOM. Changes to data are not under our control and may occur at any time. If it happens to happen before repaint, multiple renders will happen. This means wasted performance, which Vue doesn't want to see.

Therefore, Vue's queue control is well thought out (and has undergone many changes). Before that, we need to understand another important concept of event loop, microtask.

microtask

From the name, we can call it microtasks. Correspondingly, the tasks in the task queue are also called macrotasks. The names are similar, but the properties are different.

Each event loop contains a microtask queue. After the loop ends, the microtasks in the queue will be executed and removed, and then the next event loop will start.

Microtasks that are added to the microtask queue after the microtask is executed will also be executed before the next event loop. That is to say, the macrotask can only be executed after all the microtasks are executed, and the microtask has a higher priority.

This feature of microtask is simply the best choice for queue control! Vue also calls nextTick to do asynchronous queue control internally for DOM update. When we call nextTick ourselves, it appends our own callback function after the microtask that updates the DOM, thereby ensuring that our code is executed after the DOM is updated, and avoiding the possible multiple execution problems of setTimeout.

Common microtasks are: Promise, MutationObserver, Object.observe (abandoned), and process.nextTick in nodejs.

Huh? It seems that I have seen MutationObserver. Does it mean that Vue uses MO to use its microtask feature, not to do DOM monitoring? Yep, that's it. The core is microtask, you can use MO or not. In fact, Vue has deleted the MO-related code in version 2.5, because it is a new feature of HTML5, and there are still bugs on iOS.

Then the optimal microtask strategy is Promise, and the embarrassing thing is that Promise is a new addition to ES6, and there are also compatibility problems~ So Vue faces a downgrade strategy.

Vue's downgrade strategy

As we mentioned above, the best choice for queue control is microtask, and the best choice for microtask is Promise. But if the current environment does not support Promise, vue has to be downgraded to macrotask for queue control.

What options are there for macrotask? Mentioned earlier that setTimeout is one, but it's not an ideal solution. Because the minimum time interval for setTimeout execution is about 4ms, there is a slight delay. Are there other options?

Don't sell it. In the source code of vue2.5, the macrotask downgrade scheme is: setImmediate, MessageChannel, setTimeout.

setImmediate is the most ideal solution, but unfortunately only IE and nodejs support it.

The onmessage callback of MessageChannel is also a microtask, but it is also a new API, facing the embarrassment of compatibility...

So the final solution is setTimeout. Although it has execution delay, it may cause multiple renderings, which is a no-brainer.

Summarize

The above is the implementation principle of vue's nextTick method. To summarize:

  1. Vue uses an asynchronous queue to control the DOM update and the nextTick callback to be executed successively

     

  2. Because of its high priority, microtask can ensure that the microtasks in the queue are executed before an event loop.

     

  3. Due to compatibility issues, vue had to do a downgrade from microtask to macrotask

 

Relevant information:

event loop standard

https://html.spec.whatwg.org/multipage/webappapis.html#event-loops

vue2.5's nextTick change record

https://github.com/vuejs/vue/commit/6e41679a96582da3e0a60bdbf123c33ba0e86b31

Source code analysis article

https://github.com/answershuto/learnVue/blob/master/docs/Vue.js%E5%BC%82%E6%AD%A5%E6%9B%B4%E6%96%B0DOM%E7%AD%96%E7%95%A5%E5%8F%8AnextTick.MarkDown

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324687667&siteId=291194637