Ask a common question about data responsiveness:

What are the two outputs console.log(p1.innerhtml) in the following sample code? Why is that?

<! DOCTYPE html> <html> <body> <div id="demo"> <h1> Asynchronous update </h1> <p id="p1">{{foo}}</p>
    </div>
    <script>
        const app = new Vue({
            el: '#demo',
            data: { foo: ' ' },
            mounted() {
                setInterval(() => {                    
                    this.foo = 't1'
                    this.foo = 't2'
                    this.foo = 't3'Console. log(p1.innerhtml) // What is the display value of the page? this.$nextTick(() => {console.log(p1.innerhtml) // At this point, the display value of the page? })}, 1000); }}); </script> </body> </html>Copy the code

The first question, “What?”, is not complicated. The hard part is why. The nature of the problem involves the asynchronous updating of the Vue.

First, it should be clear that Vue updates the DOM asynchronously and in batches. The reason for this is simple: updating the DOM is expensive and costly. As shown in the example above, will the DOM be updated three times in a row inside the Vue? That’s obviously unreasonable. Batch, asynchronous operations are more elegant.

We want to look at the source code, Vue update DOM batch and asynchronous operation, exactly how to do it?

Let’s draw a line: we’re not going to dive right into the patch algorithm for virtual DOM generation and page updates, but just want to look at the batch and asynchronous process and solve the problems just mentioned.

The source code

In defineReactive(), the core data response method, dep.notify () is called when the data changes, notifting the corresponding Watcher to perform updateComponent(), and then rendering the updated page again.

Let’s start with the notify() method of Dep.

exportdefault class Dep { static target: ? Watcher; id: number; subs: Array<Watcher>; . / / to omitnotify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
Copy the code

It executes the associated Update () method of Watcher.

import { queueWatcher } from './scheduler'

exportdefault class Watcher { ... / / to omitupdate () {
    if (this.lazy) {
      this.dirty = true
    } else if(this.sync) {// If this is synchronized this.run()}elseQueueWatcher (this) // queueWatcher's queue operation}} // The update method that is actually executed is called by schedulerrun () {
      if(this.active) {//this.get() is the updateComponent() method passed in when it is mounted const value = this.get() // If it is a component Watcher, no value is returned, // Only user - defined Watcher will enterif
      if( value ! == this.value || isObject(value) || this.deep ) { const oldValue = this.value this.value = valueif (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)}}else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }
Copy the code

See here, ask a ha: if at the same time, the component instance data in the revised many times, the Watcher will also perform queueWatcher (this) many times, so will exist in the current queue multiple the same Watcher?

With this problem in mind, look at the queueWatcher() method of schedule.js in the same folder:

export functionQueueWatcher (watcher: watcher) {const id = watcherif (has[id] == null) {
    has[id] = true
    if(! flushing) { queue.push(watcher) }else {
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    if(! waiting) { waiting =true

      if(process.env.NODE_ENV ! = ='production' && !config.async) {
        flushSchedulerQueue()
        returnNextTick (flushSchedulerQueue)}}Copy the code

Each Watcher will have an ID, and only the new Watcher will join the team. The batch process, as we’ve seen, will be to put Watcher into a queue, and then batch update.

If the Watcher is queued for multiple updates, only the first update will be queued. If the Watcher is queued for multiple updates, only the first update will be queued.

Answer: Not really, data responsiveness is always going on, and the changing data is always changing. The need to clarify its association with the batch update queue occurs on Watcher’s run() method. When the run() method is executed, it gets the latest data.

So batch, what about asynchrony? Let’s take a look inside the nextTick() function to learn something about asynchronous operations:


export let isUsingMicroTask = false

const callbacks = []
let pending = false

function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (leti = 0; i < copies.length; I ++) {copies[I]()}} // About timerFunc selection processletTimerFunc // Prioritize Promises because promises are based on microtasksif(typeof Promise ! = ='undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true// MutationObserver is also microtask-based}else if(! isIE && typeof MutationObserver ! = ='undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  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)
  }
  isUsingMicroTask = true// If neither of the above is possible, choosesetImmediate(), which is based on macro tasks}else if (typeof setImmediate ! = ='undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else{// The most helpless choice, choicesetTimeout
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)}} //nextTick: Performs queue operations in accordance with the specific asynchronous policy timerFunc()export functionnextTick (cb? : Function, ctx? : Object) {let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')}}else if (_resolve) {
      _resolve(ctx)
    }
  })
  if(! pending) { pending =true
    timerFunc()
  }
}
Copy the code

See more interesting pages on macro and micro tasks:

Juejin. Cn/post / 684490…

Jakearchibald.com/2015/tasks-…


Vue source code interpretation

(1) : Vue constructor and initialization process

(II) : Data response and implementation

(3) : array responsive processing

(iv) Vue’s asynchronous update queue

(5) The introduction of virtual DOM

(VI) Data update algorithm — Patch algorithm

(7) : realization of componentization mechanism

(8) : computing properties and listening properties

The Optimize stage of the compilation process

Vue

Vue’s error handling mechanism

Parse the working process of Vue in handwritten code

Vue Router handwriting implementation