1. Introduction

This article is mainly to sort out the mechanism of asynchronous update in VUE, which will involve the running mechanism of JS, if you do not know, you can first take a look at the JS event loop.

This article mainly combs from two questions

  1. Vue’s asynchronous update mechanism
  2. Implementation principle of $nextTick

2. Basic usage

<div id="app">{{ msg }}</div>

new Vue({
  el: '#app'.data(){
    return {
      msg: 'hello vue'}},mounted() {
    this.msg = '1234'
    console.log(this.msg) / / 1234
    // The MSG is updated, but it is still hello vue
    // console.log(document.getElementById('app').innerText) // hello vue
    
    // If $nextTick is used, the latest value is obtained
    this.$nextTick(() = > {
      console.log(document.getElementById('app').innerText) / / 1234})}Copy the code

Vue’s implementation of responsiveness does not mean that the DOM changes as soon as the data changes. After Vue modifies data, the view is not updated immediately. Instead, the view is updated uniformly after all data changes in the same event cycle are completed. That is, vUE is rendered asynchronously

  1. this.msg = '1234'. Because data hijacking is done at initialization, when the data is updated, the interception of the setter is triggered (vUE checks if the new value is equal to the old value, and if it is not, the update is triggered), and if it is not, the update is triggered. bydepnoticewatcherUpdate, which is calleddep.notify()
  2. The official Vue documentation also mentions that Vue is when DOM is updatedasynchronousTo perform. 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 tries to use native for asynchronous queuesPromise.then,MutationObserver 和 setImmediate, if the execution environment does not support itsetTimeout(fn, 0)Instead of

3. Implementation of nextTick

3.1 Entry for Asynchronous update

Dep.notify () is called after the setter is triggered when data is updated

// src/core/observer/dep.js

/** * Notify all watchers in the DEP with the watcher.update() method */
notify () {
  const subs = this.subs.slice()
  if(process.env.NODE_ENV ! = ='production' && !config.async) {
    subs.sort((a, b) = > a.id - b.id)
  }
  // Go through the watcher stored in the DEP and execute watcher.update()
  for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}
Copy the code

3.2 watcher.update() : updates

After calling dep.notify(), the update in the watcher that needs to be updated is performed. If you have a lot of Watcher, instead of updating it multiple times, you cache the watcher and update it together later

// src/core/observer/watcher.js

class Watcher {...update(){...// If you call update multiple times, cache watcher first and update it later
        MSG = 1 this. MSG = 2 this. MSG = 3 this.name = 'vue' */
        queueWatcher(this)}}Copy the code

3.4 queueWatcher

let queue = []
let has = {} // Which watcher is stored for maintenance
let pending = false // Check that all watchers in the current event loop are updated

function flushSchedulerQueue() {
    for (let i = 0; i < queue.length; i++) {
        queue[i].run()
    }
    queue = []
    has = {}
    pending = false
}

function queueWatcher(watcher) {
    const id = watcher.id
    if (has[id] == null) {
        queue.push(watcher)
        has[id] = true
        
        // Start an update operation to batch process the watcher to be updated
        if(! pending) { nextTick(flushSchedulerQueue)// this.$nextTick also calls this method
            pending = true}}}Copy the code

3.5 Implementation of nextTick

$nextTick also calls the nextTick function internally

Vue.prototype.$nextTick = function (fn) { 
    return nextTick(fn, this)}Copy the code
// The nextTick method is simple to implement
const callbacks = []
let waiting = false

// Handle all callbacks in an event loop
function flushCallbacks() {
    callbacks.forEach(cb= > cb())
    waiting = false
}

// For compatibility purposes, Vue3 does not consider compatibility
// Judge the Promise, MutationObserver, setImmediate, and setTimeout in sequence
function timer(flushCallbacks) {
    let timerFn = () = > {}
    if (Promise) {
        timerFn = () = > {
            Peomise.resolve().then(flushCallbacks)
        }
    } else if (MutationObserver) {
        let textNode = document.createTextNode(1)
        let observer = new MutationObserver(flushCallbacks)
        observer.observe(textNode, {
            characterData: true
        })
        timerFn = () = > {
            textNode.textContent = 3}}else if (setImmediate) {
        timerFn = () = > {
            setImmediate(flushCallbacks)
        }
    } else {
        timerFn = () = > {
            setTimeout(flushCallbacks)
        }
    }
    
    timerFn()
}

function nextTick(cb, ctx = null) {
    callbacks.push(cb)
    
    if(! waiting) { timer(flushCallbacks) waiting =true}}Copy the code

4. To summarize

  1. Vue updates asynchronously. As soon as it listens for changes to the data, Vue opens a queue, buffers all changes to the data in the same event loop, and finally updates the view uniformly to avoid unnecessary computation and DOM manipulation
  2. The core of the asynchronous update mechanism is realized by using the asynchronous task queue of the browser. Vue internally tries to use native for asynchronous queuesPromise.then,MutationObserver 和 setImmediate, if the execution environment does not support itsetTimeout(fn, 0)Instead of
  3. When you call UPDATE multiple times, watcher is cached and placed in a queue. That isQueueWatcher is called, queue.push(watcher). Then throughThe flushSchedulerQueue method processes the watcher that needs to be updated uniformly
  4. throughNextTick methodflushSchedulerQueuePlace a Callbacks array and handle all callbacks in an event loop