preface

Some people say: to improve performance, yes, that’s basically the case; So how does it work?

In vUE, reactive data is at the component level. That is, each update renders the entire component. If it is synchronized, the watcher will be triggered if the data property is changed. Then call the corresponding update method under watcher to update the view, and the result is obvious, too often! Suppose: The following code

// Omit redundant template syntax
data () {
	a:1.b:2.c:3
}
// If we change the data attribute according to the synchronization logic, this.a = 10; this.b = 20; this.c = 30;
Update the render view three times. Isn't that performance consuming? And the experience is not good.
Copy the code

So VUE uses asynchronous rendering. Next, let’s have a look at the principle of responsive data. If you don’t know about it, you can Go back to the data responsive.

queueWatcher

SRC/croe/observer/watcher. Js 166 rows, attributes and synchronous update first not considered here, we go down queueWatcher,

update () {
    /* istanbul ignore else */
    if (this.lazy) { // A change in the data that the attribute depends on causes the dirty of the watcher to be true
      this.dirty = true
    } else if (this.sync) { / / synchronize watcher
      this.run()
    } else {
      // Put the watcher to be updated in the queue
      // They depend on the same DEP collector. For those who don't know, see the data responsive link above
      queueWatcher(this)}}Copy the code

QueueWatcher Logical interpretation

SRC/core/observer. The scheduler. Js 164 lines, main is to implement a watcher queue, every update into the queue, and then make unified asynchronous processing. Look at the code:

export function queueWatcher (watcher: Watcher) {
  / / filter watcher
  const id = watcher.id  
  if (has[id] == null) {
    has[id] = true 
    if(! flushing) {// Put watcher in queue
      queue.push(watcher)
    } else { 
      // Select * from 'watcher.js' where id =' 1 ';
      // Assign the current id if it has been refreshed, and run the following code if the id is exceeded
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1.0, watcher)
    }
    // If not, refresh
    if(! waiting) {// Reset the status
      waiting = true
      if(process.env.NODE_ENV ! = ='production' && !config.async) {
      	// The method makes the beforUpdate method call before the refresh, and then watcher.run()
        flushSchedulerQueue() 
        return
      }
      // Key Key Key ↓ ↓
      $nextTick() => this.$nextTick()
      FlushSchedulerQueue is a function that calls the run methods of all watchers in the queue
      // watcher.run is diff generating a new DOM
      nextTick(flushSchedulerQueue)  
    }
  }
}

Copy the code

FlushSchedulerQueue: flushSchedulerQueue: flushSchedulerQueue: flushSchedulerQueue: flushSchedulerQueue: flushSchedulerQueue: flushSchedulerQueue: flushSchedulerQueue: flushSchedulerQueue: flushSchedulerQueue: flushSchedulerQueue

NextTick principle

After calling nextTick, the watcher queue callback is temporarily stored in an array of Callbacks, and then the timeFunc() method is called to execute. The key to making Watcher asynchronous is here. Let’s look at the code:

SRC/core/util/next - tick. Js 87 rows

export function nextTick (cb? :Function, ctx? :Object) { 
  let _resolve
  // Store the watcher queue and place it in the Callbacks
  callbacks.push(() = > {  
    if (cb) {
      try {
        // The callback function renders the view
        // flushSchedulerQueue => run watcher.run()
        cb.call(ctx) 
      } catch (e) {
        handleError(e, ctx, 'nextTick')}}else if (_resolve) {
      _resolve(ctx)
    }
  })
   // When the state changes, the timerFunc() method is called
  if(! pending) { pending =true
    timerFunc()  // Key, key, key! Let's go in and have a look
  }
  // $flow-disable-line
  if(! cb &&typeof Promise! = ='undefined') {
    return new Promise(resolve= > {
      _resolve = resolve
    })
  }
}
Copy the code

TimerFunc Asynchronous key

TimerFunc method SRC /core/util/next-tick.js 33 line

Clearly, it makes a judgment about the current environment, and if a PROMISE is supported, it uses the promise in order below: MutationObserver, setImmediate, and setTimeout are all asynchronous solutions, except that setTimeout is a macro task, and the other three are micro tasks.

let timerFunc
if (typeof Promise! = ='undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () = > {
    p.then(flushCallbacks)  // Callback to flushCallbacks inside then
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if(! isIE &&typeofMutationObserver ! = ='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
} else if (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
  timerFunc = () = > {
    setImmediate(flushCallbacks)   // The setImmediate callback flushCallbacks inside}}else {
  timerFunc = () = > {
    setTimeout(flushCallbacks, 0)  // flushCallbacks in setTimeout}}Copy the code

FlushCallbacks callback entity

The above timeFunc method calls back to flushCallbacks in four asynchronous ways, in turn calling the watcher.run() render view

 var isUsingMicroTask = false;
 var callbacks = [];
 var pending = false;
 
 function flushCallbacks () {
    pending = false;
    var copies = callbacks.slice(0);
    callbacks.length = 0;
    for (var i = 0; i < copies.length; i++) {
      // Callback sequencecopies[i](); }}Copy the code

conclusion

Vue is a component level update view. Each update will render the entire component. In order to improve performance, vUE uses the form of a queue and store a watcher queue. The nextTick method is then called uniformly (once and only) for update rendering.

The nextTick method uses asynchronous macro tasks and micro tasks to define a callback function.

The same goes for our usual API: vue.nexttick ().

Welcome to like, a little encouragement, a lot of growth

A link to the

  • Front-end visualization platform implementation scheme
  • Vue3 10 minutes takes you seconds to vue3
  • Vue template compilation principle
  • The vue constructor extends
  • How does VUEX state management work
  • Vue-router source analysis principle
  • Vue [how to listen for array changes]
  • Handwritten Vue response [object.defineProperty]
  • Vue responsive source sharing