Home > Web Front-end > JS Tutorial > body text

Detailed explanation of the use of Vue nextTick mechanism

php中世界最好的语言
Release: 2018-05-15 09:19:35
Original
1765 people have browsed it

This time I will bring you a detailed explanation of the use of the Vue nextTick mechanism. What are the precautions when using the Vue nextTick mechanism. The following is a practical case, let's take a look.

Let’s first look at a piece of Vue execution code:

export default {
 data () {
  return {
   msg: 0
  }
 },
 mounted () {
  this.msg = 1
  this.msg = 2
  this.msg = 3
 },
 watch: {
  msg () {
   console.log(this.msg)
  }
 }
}
Copy after login

We guess that after executing this script for 1000m, it will print: 1, 2, 3. But in actual effect, it will only be output once: 3. Why is there such a situation? Let’s find out.

queueWatcher

We define watch to listen to msg, which will actually be called by Vue like vm.$watch(keyOrFn, handler, options). $watch is a function bound to vm when we initialize it, used to create Watcher objects. Then let's take a look at how the handler is handled in Watcher:

this.deep = this.user = this.lazy = this.sync = false
...
 update () {
  if (this.lazy) {
   this.dirty = true
  } else if (this.sync) {
   this.run()
  } else {
   queueWatcher(this)
  }
 }
...
Copy after login

Initial setting this.deep = this.user = this.lazy = this.sync = false, that is, when an update is triggered, To execute the queueWatcher method:

const queue: Array<Watcher> = []
let has: { [key: number]: ?true } = {}
let waiting = false
let flushing = false
...
export function queueWatcher (watcher: Watcher) {
 const id = watcher.id
 if (has[id] == null) {
  has[id] = true
  if (!flushing) {
   queue.push(watcher)
  } else {
   // if already flushing, splice the watcher based on its id
   // if already past its id, it will be run next immediately.
   let i = queue.length - 1
   while (i > index && queue[i].id > watcher.id) {
    i--
   }
   queue.splice(i + 1, 0, watcher)
  }
  // queue the flush
  if (!waiting) {
   waiting = true
   nextTick(flushSchedulerQueue)
  }
 }
}
Copy after login

The flushSchedulerQueue function in nextTick(flushSchedulerQueue) is actually the watcher's ViewUpdate:

function flushSchedulerQueue () {
 flushing = true
 let watcher, id
 ...
 for (index = 0; index < queue.length; index++) {
  watcher = queue[index]
  id = watcher.id
  has[id] = null
  watcher.run()
  ...
 }
}
Copy after login

In addition, regarding the waiting variable, this It is a very important flag, which ensures that the flushSchedulerQueue callback is only allowed to be placed in callbacks once. Next, let's take a look at the nextTick function. Before talking about nexTick, you need to have a certain understanding of Event Loop, microTask, and macroTask. Vue nextTick also mainly uses these basic principles. If you don’t understand it yet, you can refer to my article Introduction to Event Loop. Now let’s take a look at its implementation:

export const nextTick = (function () {
 const callbacks = []
 let pending = false
 let timerFunc
 function nextTickHandler () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
   copies[i]()
  }
 }
 // An asynchronous deferring mechanism.
 // In pre 2.4, we used to use microtasks (Promise/MutationObserver)
 // but microtasks actually has too high a priority and fires in between
 // supposedly sequential events (e.g. #4521, #6690) or even between
 // bubbling of the same event (#6566). Technically setImmediate should be
 // the ideal choice, but it&#39;s not available everywhere; and the only polyfill
 // that consistently queues the callback after all DOM events triggered in the
 // same loop is by using MessageChannel.
 /* istanbul ignore if */
 if (typeof setImmediate !== &#39;undefined&#39; && isNative(setImmediate)) {
  timerFunc = () => {
   setImmediate(nextTickHandler)
  }
 } else if (typeof MessageChannel !== 'undefined' && (
  isNative(MessageChannel) ||
  // PhantomJS
  MessageChannel.toString() === '[object MessageChannelConstructor]'
 )) {
  const channel = new MessageChannel()
  const port = channel.port2
  channel.port1.onmessage = nextTickHandler
  timerFunc = () => {
   port.postMessage(1)
  }
 } else
 /* istanbul ignore next */
 if (typeof Promise !== 'undefined' && isNative(Promise)) {
  // use microtask in non-DOM environments, e.g. Weex
  const p = Promise.resolve()
  timerFunc = () => {
   p.then(nextTickHandler)
  }
 } else {
  // fallback to setTimeout
  timerFunc = () => {
   setTimeout(nextTickHandler, 0)
  }
 }
 return function queueNextTick (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()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
   return new Promise((resolve, reject) => {
    _resolve = resolve
   })
  }
 }
})()
Copy after login

First, Vue simulates the event queue through callback array , events in the event team are called through the nextTickHandler method, and what is executed is determined by timerFunc. Let's take a look at the definition of timeFunc:

if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
   setImmediate(nextTickHandler)
  }
 } else if (typeof MessageChannel !== 'undefined' && (
  isNative(MessageChannel) ||
  // PhantomJS
  MessageChannel.toString() === '[object MessageChannelConstructor]'
 )) {
  const channel = new MessageChannel()
  const port = channel.port2
  channel.port1.onmessage = nextTickHandler
  timerFunc = () => {
   port.postMessage(1)
  }
 } else
 /* istanbul ignore next */
 if (typeof Promise !== 'undefined' && isNative(Promise)) {
  // use microtask in non-DOM environments, e.g. Weex
  const p = Promise.resolve()
  timerFunc = () => {
   p.then(nextTickHandler)
  }
 } else {
  // fallback to setTimeout
  timerFunc = () => {
   setTimeout(nextTickHandler, 0)
  }
 }
Copy after login

You can see the priority of definition of timerFunc macroTask --> microTask, in an environment without Dom, use microTask, such as weex

setImmediate, MessageChannel VS setTimeout

We define setImmediate and MessageChannel first. Why should we use them first to create macroTask instead of setTimeout? HTML5 stipulates that the minimum time delay of setTimeout is 4ms, which means that under ideal circumstances, the fastest asynchronous callback can trigger is 4ms. Vue uses so many functions to simulate asynchronous tasks, with only one purpose, which is to make the callback asynchronous and called as early as possible. The delays of MessageChannel and setImmediate are obviously smaller than setTimeout.

Solution to the problem

With these foundations in mind, let’s look at the problems mentioned above again. Because Vue's event mechanism schedules execution through the event queue, it will wait for the main process to be idle before scheduling, so go back and wait for all processes to complete before updating again. This kind of performance advantage is obvious, for example:

Now there is a situation where the value of test will be looped executed 1000 times when mounted. Each time, setter->Dep->Watcher->update->run will be triggered responsively. If the view is not updated asynchronously at this time, the DOM will be directly manipulated to update the view every time, which is very performance consuming. Therefore, Vue implements a queue, and the run of the Watcher in the queue will be executed uniformly on the next Tick (or the microtask phase of the current Tick). At the same time, Watchers with the same ID will not be added to the queue repeatedly, so the Watcher run will not be executed 1,000 times. The final update of the view will only directly change the DOM corresponding to test from 0 to 1000. It is guaranteed that the action of updating the view to operate the DOM is called at the next Tick (or the microtask phase of the current Tick) after the current stack is executed, which greatly optimizes performance.

Interesting question

var vm = new Vue({
  el: '#example',
  data: {
    msg: 'begin',
  },
  mounted () {
   this.msg = 'end'
   console.log('1')
   setTimeout(() => { // macroTask
     console.log('3')
   }, 0)
   Promise.resolve().then(function () { //microTask
    console.log('promise!')
   })
   this.$nextTick(function () {
    console.log('2')
   })
 }
})
Copy after login

Everyone must know the execution order of this and print it in sequence: 1, promise, 2, 3.

  1. Because this.msg = 'end' is triggered first, the watcher's update is triggered, thereby pushing the update operation callback into the vue event queue.

  2. this.$nextTick also enters a new callback function for event queue push. They all come through setImmediate --> MessageChannel --> Promise --> setTimeout Define timeFunc. Promise.resolve().then is a microTask, so it will print the promise first.

  3. When MessageChannel and setImmediate are supported, their execution order takes precedence over setTimeout (in IE11/Edge, the setImmediate delay can be within 1ms, while setTimeout has a minimum delay of 4ms, so setImmediate executes the callback function earlier than setTimeout(0). Secondly, because in the event queue, the callback array is received first), so it will print 2, and then print 3

  4. but In the case where MessageChannel and setImmediate are not supported, timeFunc will be defined through Promise, and the old version of Vue before 2.4 will execute promise first. This situation will cause the order to become: 1, 2, promise, 3. Because this.msg must first trigger the dom update function, the dom update function will first be collected by the callback into the asynchronous time queue, and then Promise.resolve().then(function () { console.log('promise!')} will be defined. ) such a microTask, and then defining $nextTick will be collected by the callback. We know that the queue satisfies the first-in-first-out principle, so the objects collected by the callback are executed first.

Postscript

If you are interested in Vue source code, you can come here: More interesting Vue convention source code explanations

I believe you have mastered the method after reading the case in this article. For more exciting information, please pay attention to other related articles on the php Chinese website!

Recommended reading:

JS to implement transparency gradient function

##jQuery traversal of XML nodes and attributes implementation steps

The above is the detailed content of Detailed explanation of the use of Vue nextTick mechanism. For more information, please follow other related articles on the PHP Chinese website!

source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template