As mentioned in my previous article, VUE renders pages asynchronously. When we want to get the value of the page after rendering, it is better to get it in the micro task because the macro task is slow. Vue provides us with two functions: vm.\$nextTick and vue. nextTick.

VUE executes $nextTick immediately after the page is re-rendered and the DOM is updated, and we can pass the callback function in $nextTick to get the value we want.

The use of nextTick

The basic use

<div id="app">{{ msg }}</div>
Copy the code
const vm = new Vue({
el: '#app'.data: {
msg: 'cookies'
}
})
vm.msg = 'Hurry up and eat the pie';
console.log(vm.msg); // The data has been changed
// 1. Use vm.$nextTick instance method
console.log(vm.$el.innerHTML) // The dom data has not been updated yet
vm.$nextTick((a)= > {
    console.log(vm.$el.innerHTML); // The dom data has been updated
})
// Multiple callback functions can be executed together
vm.$nextTick((a)= > {
    vm.msg = vm.$el.innerHTML + 1 // The page is re-rendered as Quick eat pie 1
    console.log(vm.$el.innerHTML) // Hurry up and eat the cake
})
// 2. Use the vue. nextTick constructor method
Vue.nextTick((a)= > {
    console.log(vm.$el.innerHTML); // Hurry up and eat the cake
})
Copy the code

Vm. $nextTick and vue. nextTick can also be used as promises to get data in THEN

<div id="app">{{ msg }}</div>
Copy the code
const vm = new Vue({
    el: '#app'.data: {
        msg: 'cookies'
    }
})
vm.msg = 'Hurry up and eat the pie';
// 1. Use vm.$nextTick
vm.$nextTick().then((a)= > {
    console.log(vm.$el.innerHTML); // Hurry up and eat the cake
})
// 2. Use vue.nexttick
Vue.nextTick().then((a)= > {
    console.log(vm.$el.innerHTML); // Hurry up and eat the cake
})
Copy the code

Difference between vm.$nextTick and vue. nextTick

The this of vue. nextTick internal function points to the window

Vue.nextTick(function () {
    console.log(this); // window
})
Copy the code

The this of the vm.$nextTick internal function refers to the Vue instance object, which is typically used

vm.$nextTick(function () {
    console.log(this); / / vm instances
})
Copy the code

How is nextTick implemented

NextTick: MutationObserver is just floating around, microTask is the core! It’s very well written and easy to understand. I’m basically just repeating it in my own words to clear my head.

To review: Asynchronous tasks are divided into macro tasks and micro tasks, but macro tasks are slow (setTimeout, etc.) and micro tasks are fast (promise.then (), etc.).

setTimeout((a)= > {
  console.log('timeout');
}, 0)  
Promise.resolve().then((a)= > {
  console.log('promise');
})
// Console print order: promise > timeout
Copy the code

Then let’s talk about microtasks:

Mutation Events

It would be nice to have a brief understanding of this part, he is not the protagonist heh heh.

Mutation Events are defined in DOM3 to listen for Events that change the STRUCTURE of the DOM tree. His usage is particularly simple and easy to understand, as follows:

// Listen for child changes to the list element
document.getElementById('list').addEventListener("DOMSubtreeModified".function(){
  console.log('List neutrons have been modified');
}, false);
Copy the code

Although he looks great, there are actually a lot of problems:

  1. Browser Compatibility Issues

    1. Previous versions of IE9 did not support mutation events and some of them were not implemented correctly in IE9 versions
    2. The Webkit kernel does not support the DOMAttrModified feature provided by Mutation Events (cannot listen for element attribute changes)
    3. DOMElementNameChanged and DOMAttributeNameChanged are not supported on Firefox (up to Version 11), and probably other browsers as well.
  2. Performance issues

    1. slow

      MutationEvents are Events themselves, so capture is performed as an event bubble (using addEventListener). If other MutationEvents are triggered during the bubble capture, it is likely to block the Javascript thread. Slows down your browser, or even crashes it.

    2. redundant

      Because Mutation Events are executed synchronously, however, the DOM is likely to change frequently. If 1000 consecutive p elements are inserted into the document, 1000 consecutive insert events are triggered, executing the callback function for each event, which is likely to cause the browser to lag.

    3. Prone to collapse

      All events in a MutationEvent are designed to be uncancelable. If you cancel the MutationEvent event, the existing DOM interface will not be able to make changes to the document. DOM operations like appendChild and remove add and remove nodes will become invalid.

      Knowing that a MutationEvent cannot be cancelled, consider the following example:

      // Listen for all nodes added to document
      document.addEventListener('DOMNodeInserted'.function() {
          var newEl = document.createElement('div');
          document.body.appendChild(newEl);
      });
      Copy the code

      All DOM addition operations under document trigger the DOMNodeInserted method, at which point a circular call to the DOMNodeInserted method occurs, causing the browser to crash.

If you are interested, you can also take a look at this and explain why Mutation Events were replaced. Mainly according to the logic of the summary here, but I have a good idea of my English level, if there is a big guy found my summary is wrong welcome to put forward! DOM Mutation Events Replacement: The Story So Far / Existing Points of Consensus

Mutation Observer

Mutation Observer (Mutation Observer) is a new API in HTML5, which is not so easy to use because it is relatively advanced. This interface can listen for changes in the DOM tree. Use as follows:

//MO is called when it listens for DOM changes and executes the callback we passed in
let MO = new MutationObserver(callback)
Copy the code

The previous step only defined a callback function. It also provided an observe method to define the node and content to listen on. There are two parameters:

  1. The DOM element to listen on.
  2. Listen for changes to the element.

When the content specified by this element is changed, MO is called and the callback function is executed.

varDomTarget = the DOM node you want to listen on mo.observe(domTarget, {characterData: true
     // Listen for text changes
     // It will only be observed if the node data is changed. It will not be observed if you delete or add nodes
})
Copy the code

Now any text changes that occur on the domTarget will be heard by MO, which will trigger the callback that you pass in the New MutationObserver(callback).

The advantages of Mutation Observer are as follows:

  1. MutationObserver offers a great deal more flexibility than MutationEvent, allowing a variety of options to be set to suit the programmer’s observation of the target.

  2. Asynchronous execution, which means that no matter how many times you’ve changed the DOM in the previous synchronous task, I only call the callback after all the DOM operations are complete.

    Must pay attention! The MutationObserver callback is executed in a microqueue.

  3. You can call the observe.disconnect() method to cancel the observation when you no longer want to observe the change of the target node.

The realization of the nextTick

Look at the source code, in fact, really not much, mainly is more notes, endure a endure to read:

export const nextTick = (function () {
  // Callbacks hold all the callbacks that we want to execute after dom updates
  var callbacks = []
  // Pending = pending; // Pending = pending
  var pending = false
  // Which asynchronous function is used to perform: MutationObserver, setImmediate or setTimeout
  var timerFunc
  // All callbacks are executed
  function nextTickHandler () {
    pending = false
    // The reason why we need to slice a copy is because some CB execution processes add content to the callbacks
    $nextTick = $nextTick
    // These should be implemented in the next nextTick,
    // So copy the current one and iterate through it to avoid endless execution
    var copies = callbacks.slice(0)
    // Empty the callback function because it's all out for execution
    callbacks = []
    // Execute all callbacks
    for (var i = 0; i < copies.length; i++) {
      copies[i]()
    }
  }
    
  // there is a bug in the MutationObserver of WebView in ios9.3.
  // So the hasMutationObserverBug stores whether this is the case
  if (typeofMutationObserver ! = ='undefined' && !hasMutationObserverBug) {
    // Declare a random variable as the node of the text
    var counter = 1
    var textNode = document.createTextNode(counter)
    // Create a MutationObserver that listens for dom changes and executes a callback to nextTickHandler
    var observer = new MutationObserver(nextTickHandler)
    // Call the interface of MutationObserver to monitor character changes in text nodes
    observer.observe(textNode, {
      characterData: true
    })
    // Each time timerFunc is executed, the character of the text node is changed so that the Observer listens, and nextTickHandler is executed, which executes the callback function in the callback.
    timerFunc = function () {
      // Both switch the contents of the text node between 0/1
      counter = (counter + 1) % 2
      // Assign the new value to the text node we observed using the observer
      textNode.data = counter
    }
  } else {
    // Webpack inserts setImmediate shim in the code by default
    // Without MutationObserver, setImmediate is the best mediate
    SetImmediate is a macro task, but it performs a bit faster than setTimeout. SetImmediate is only available in IE, mainly for compatibility with IE.
    const context = inBrowser
      ? window
      : typeofglobal ! = ='undefined' ? global : {}
    timerFunc = context.setImmediate || setTimeout
  }
  // This returns the nextTick content
  return function (cb, ctx) {
    // There is no second argument passed
    var func = ctx
      // If so, change this to the second argument of the callback function
      ? function () { cb.call(ctx) }
      // If not, just assign the callback function to func
      : cb
    // Put the callbacks inside the callbacks and wait for dom updates
    callbacks.push(func)
    // If pending is true, timerFunc has already been executed in this cycle
    if (pending) return
    / / lock
    pending = true
    // Execute the asynchronous function in which all callbacks are executed
    timerFunc(nextTickHandler, 0)}}) ()Copy the code

Thanks for your comments, I finally understand what pending is

First, when we first call nextTick in our code, we perform the process of pushing the callback into the Callbacks and calling the asynchronous nextTickHandler, setting pending = true. So when we call nextTick, we call callbacks.push(func), push the asynchronous function to the callbacks and stop calling the asynchronous function. After all, asynchronous functions wait until the time to execute, the callbacks will complete the function, so there is no need to call multiple times.

When pending is on (false), it says: I put nextTickHandler on the asynchronous queue! You need to put the callback in for him to execute! Pending = true; pending = true; Putting in the callback function! Finally the nextTickHandler executes all callbacks as soon as the microtask is executed.

If you don’t open the pending function, it will be locked, and it will be storing the pending function. If you don’t put the nextTickHandler on the asynchronous queue, it will be storing so many callbacks. So we finally open pending so that when we call nextTick in a macro task such as setTimeout, we can call nextTickHandler and execute the callback function.

NextTickHandler is a function that executes after nextTickHandler.

In summary, the main idea behind nextTick is that it’s possible to change the DOM multiple times during a synchronization task. After all the synchronization tasks are completed, it means that the data modification is finished. I have performed all the functions to change the DOM, and I have obtained the DOM data to render, so I can update the DOM at this time. So nextTick’s callbacks are executed in MicroTask. In this way, repeated modification and rendering of a DOM element can be avoided as far as possible. On the other hand, DOM operations can be concentrated to reduce The Times of rendering and improve the efficiency of DOM rendering. After all the microtasks have been performed, the page is rendered.

Microtasks and macro tasks

The Mutation Observer is not necessary. The focus is on microtasks, and any asynchronous function that will be placed on a microqueue can be considered. In the latest Vue source code, promise.resolve ().then(nextTickHandler) is preferred to put asynchronous callbacks into microTask, and MO is used only if there are no native promises (Promises cannot be implemented in IE).

Why microtasks? Are macro tasks ok? It could be, but it’s a little slow.

Direct originally wanted to directly take Vue source code in detail nextTick example, but found that the connection can not go, I tried to achieve a, but the reduction of where said the effect, so can only rely on understanding.. If there is improper understanding of the place but also trouble big men pointed out.

The scenario is as follows:

UI events in HTML, network events, HTML Parsing, and so on are done using macro tasks. Let’s say we listen for an element’s Scroll event, use the nextTick in the Scroll callback, and modify the DOM in the nextTick callback.

So once the scroll event of the element is triggered, the scroll callback will be placed in the macro queue for execution. When the Scroll callback is executed, the nextTick will be executed, and the nextTick will put the DOM modification task into the macro queue or microqueue.

If nextTick uses microtasks, all microtasks will be executed immediately after the macro task (the Scroll callback) completes, modifying the DOM in a timely manner, and rendering the DOM after the microtask completes. If nextTick uses macro tasks, the DOM will be modified in a later macro task after the current macro task and all microtasks have been executed, which is a bit slow and will miss multiple redraw and render UI triggers.

There may also be multiple macro queues inside the browser to respond more quickly to the user UI. The UI macro queue may have a higher priority, so if nextTick uses macro tasks, it is possible that the UI macro task has been executed several times and the nextTick macro task has not been executed to modify the DOM, resulting in a delay in updating the DOM. Therefore, using macro tasks to implement nextTick is not feasible.

UI Render after each event loop round

  1. Selects the oldest macro task from one of the macro queues. (Because there are multiple macro queues, browsers can preferentially and frequently execute tasks in certain macro queues, such as UI macro queues.)

  2. Execute the macro task.

  3. Complete all microtasks in the microqueue. If a microtask is added during microtask execution, the newly added microtask will still be executed. (But there seems to be a limit?)

  4. To render the page

    The Document object associated with the event loop of the current round forms a list waiting to update the UI. But not all associated documents need to update the UI, and the browser decides if the document will benefit from UI Render.

    If we’re listening for scroll events, then every time we’re scoll, document or DOM will immediately scroll, and then those scroll elements will be added to the object that triggered the Scroll event, and those objects will be iterated over and trigger the Scroll event.

    The resize event is then fired in a similar manner. Subsequent media queries, updating CSS animations and sending events, and determining whether there is a full-screen operation are all similarly handled, triggering events.

    This is followed by the requestAnimationFrame callback (the new HTML5 one, the 60 frames per second one, which is a bit like setTimeout) and the IntersectionObserver callback (which can tell if an element has entered the “viewport”, i.e. whether the user can see it).

    Finally, render the UI.

  5. Continue with the Event Loop to perform macro tasks, micro tasks, and interface rendering.

Image from the Big Guy blog: looks like it’s easier to understand than words.

The disadvantage of vue

From the long list of clutter above, we can clearly see that the page rendering process is always waiting. He always waits for the main thread to finish rendering, so if a task or function on the main thread goes wrong and gets stuck there, the whole page gets stuck. As long as the main thread is not executed, he has no chance to render.

But because his positioning is small and medium-sized enterprises, so he does not care about this problem.

Refer to the article

  1. MutationObserver listens for DOM tree changes
  2. Listen for Dom node changes – Mutation Observer
  3. Learn about MutationObserver in HTML5
  4. NextTick: MutationObserver is just floating around, microTask is the core!
  5. IntersectionObserver API usage tutorial
  6. Web front-end advanced part (2) browser Webpack