In vUE source code learning 11: Responsive principles and dependency collection article, the use of Watcher classes and De classes to achieve data and view responsiveness, and understand how vUE is dependent collection.

This leaves one problem: if you update a Watcher frequently, the page will render frequently.

In the vUE source code, is the use of an asynchronous mechanism for page updates. Today we are going to learn the principle of asynchronous update

Performance issues to solve

There is an update method in Watcher that is executed multiple times as the data is frequently updated.

update() {
    // This method will operate frequently
    this.get()
}

get() {
    // What get does is render the page
    pushTarget(this)
    this.getter()
    popTarget()
}
Copy the code

We know that the this.getter method calls the following method

updateComponent = () = > {
    // 1. Generate the virtual DOM with render
    vm._update(vm._render()) // Subsequent updates can invoke the updateComponent method
    // 2. Virtual Dom generates real Dom
}
Copy the code

The result is very poor performance. To improve efficiency, Vue did two things:

  • Cache watcher every time you update it
  • If multiple times with the new is a Watcher, merge it into one and render the page together

queueWatcher

QueueWatcher As the name implies, this is a watcer queue.

We created a scheduler.js in the Observer folder

Scheduler is a JS file about a scheduler

// Schedule work scheduler.js
import { nextTick } from '.. /util';
// 1. Remove weight 2
let queue = []
let has = {} // The list maintains which watcher is stored
function flushSchedulerQueue() {
    for (let i = 0; i < queue.length; i++) {
        queue[i].run()
    }
    queue = []
    has = {}
    pending = false
}
let pending = false
export function queueWatcher(watcher) {
    // Multiple updates will receive multiple watcher
    const id = watcher.id
    if (has[id] == null) {
        queue.push(watcher)
        has[id] = true
        // Start batch update operation (anti-shake)
        if(! pending) { nextTick(flushSchedulerQueue,0)
            pending = true}}}Copy the code

The related variable methods in this file scheduler.js function as follows:

  • Queue: A queue for the watcher to be changed
  • Has: this is used to store the id of watcher. The id value is used to remove the duplicate. If the ID exists in this has object, it is no longer wanted to store in queue, otherwise it would be stored in queue
  • FlushSchedulerQueue: flushes the scheduling queue to render all watchers in the queue. At the same time, reset the parameters related to scheduling, includingqueue,has,pending
  • Pending: In rendering, this is a lock-like concept. If pending is false, start a batch operation until all watchers have been executed, and the pending state is reset

NextTick method

A nextTick method is used in scheduler, as shown below

const callbacks = []

function flushCallbacks() {
    callbacks.forEach(cb= > cb())
    waiting = false
}

function timer(flushCallbacks) {
    let timerFn = () = >{}if (Promise) {
        timerFn = () = > {
            Promise.resolve().then(flushCallbacks)
        }
    } else if (MutationObserver) {
        // This is also a microtask
        let textNode = document.createTextNode(1)
        let observe = new MutationObserver(flushCallbacks)
        observe.observe(textNode, {
            characterData: true
        })
        timerFn = () = > {
            textNode.textContent = 3}}else if (setImmediate) {
        timerFn = () = > {
            setImmediate(flushCallbacks)
        }
    } else {
        timerFn = () = > {
            setTimeout(flushCallbacks, 0)
        }
    }
    timerFn()
}

export function nextTick(cb) {
    callbacks.push(cb) $nextTick($nextTick); $nextTick($nextTick);
    if(! waiting) { timer(flushCallbacks) waiting =true}}Copy the code

A simple way to interpret this code is to use a microtask or macro task to execute a set of Watcher tasks. The code in the Timer method is compatible with the handling of browser microtasks and macro tasks. This process is not compatible with Vue3.0.

Detailed combing is as follows:

A flushSchedulerQueue callback cb is passed into scheduler.js and stored in the callbacks queue.

If the state is not waiting, start a microtask (macro task if incompatible) to execute the callback and then reset the wating state

$nextTick

This.$nextTick(), which is often used in Vue, uses the nextTick method.

That is, a nextTick method is mounted on prototype at vUE initialization.

import { nextTick } from './util';
export function lifecycleMixin(Vue) {
    / /... Other code
    Vue.prototype.$nextTick = nextTick
}
Copy the code

Thus, we can call the $nextTick method from an instance of vue.

Well, that’s the end of today’s lesson, and I’m looking forward to learning how to update arrays next time.

Historical articles

  • How does CodeGen convert ast syntax trees into Render strings?
  • Vue source code learning 9: virtual Dom implementation principle
  • Vue source code learning 10: create a virtual DOM into a real DOM
  • Vue source code learning 11: Responsive principles and dependency collection