Maison  >  Article  >  interface Web  >  Explication détaillée de l'utilisation du mécanisme Vue nextTick

Explication détaillée de l'utilisation du mécanisme Vue nextTick

php中世界最好的语言
php中世界最好的语言original
2018-05-15 09:19:351757parcourir

Cette fois, je vais vous apporter une explication détaillée de l'utilisation du mécanisme Vue nextTick. Quelles sont les précautions lors de l'utilisation du mécanisme Vue nextTick. Ce qui suit est un cas pratique, jetons un coup d'œil.

Regardons d'abord un morceau de code d'exécution de Vue :

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

Nous supposons qu'après avoir exécuté ce script, il s'imprimera dans l'ordre : 1, 2, 3 après 1000 m. Mais en réalité, il ne sera émis qu’une seule fois : 3. Pourquoi cela arrive-t-il ? Découvrons-le.

queueWatcher

Nous définissons watch pour écouter msg, qui sera en fait appelé par Vue comme vm.$watch(keyOrFn, handler, options). $watch est une fonction liée à vm lorsque nous l'initialisons, utilisée pour créer des objets Watcher. Voyons ensuite comment le gestionnaire est géré dans 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)
  }
 }
...

Paramètre initial this.deep = this.user = this.lazy = this.sync = false, c'est-à-dire lorsque la mise à jour est déclenché, exécutera la méthode queueWatcher :

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)
  }
 }
}

La fonction flushSchedulerQueue dans nextTick(flushSchedulerQueue) ici est en fait la mise à jour de la vue de l'observateur :

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()
  ...
 }
}

En plus Concernant la variable d'attente, il s'agit d'un indicateur très important, qui garantit que le rappel flushSchedulerQueue ne peut être placé qu'une seule fois dans les rappels. Jetons ensuite un coup d'œil à la fonction nextTick. Avant de parler de nexTick, vous devez avoir une certaine compréhension de Event Loop, microTask et macroTask. Vue nextTick utilise également principalement ces principes de base. Si vous ne le comprenez pas encore, vous pouvez vous référer à mon article Introduction à Event Loop. Voyons maintenant son implémentation :

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
   })
  }
 }
})()

Tout d'abord, Vue le simule via un rappel array<.> File d'attente des événements, les événements de la file d'attente des événements sont appelés via la méthode nextTickHandler et ce qui est exécuté est déterminé par timerFunc. Jetons un coup d'oeil à la définition de 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)
  }
 }
Vous pouvez voir la priorité de définition de timerFunc macroTask --> microTask, dans un environnement sans Dom, utilisez microTask, comme weex

setImmediate, MessageChannel VS setTimeout

Nous définissons d'abord setImmediate et MessageChannel Pourquoi devrions-nous les utiliser en premier pour créer une macroTask au lieu de setTimeout ? HTML5 stipule que le délai minimum de setTimeout est de 4 ms, ce qui signifie que dans des circonstances idéales, le rappel asynchrone le plus rapide pouvant se déclencher est de 4 ms. Vue utilise de nombreuses fonctions pour simuler des tâches asynchrones, avec un seul objectif : rendre le rappel asynchrone et appelé le plus tôt possible. Le délai de MessageChannel et setImmediate est évidemment inférieur à setTimeout.

Résoudre les problèmes

Avec ces fondements à l'esprit, examinons à nouveau les problèmes mentionnés ci-dessus. Étant donné que le mécanisme d'événements de Vue planifie l'exécution via la file d'attente des événements, il attendra que le processus principal soit inactif avant de planifier, alors revenez en arrière et attendez que tous les processus soient terminés avant de procéder à une nouvelle mise à jour. Cet avantage en termes de performances est évident, par exemple :

Il existe désormais une situation où la valeur de test sera exécutée 1000 fois par ++

loop une fois montée. Chaque fois que ++ est déclenché, setter->Dep->Watcher->update->run sera déclenché en réponse. Si la vue n'est pas mise à jour de manière asynchrone à ce moment-là, alors ++ fera fonctionner directement le DOM pour mettre à jour la vue à chaque fois, ce qui consomme beaucoup de performances. Par conséquent, Vue implémente une file d'attente et l'exécution du Watcher dans la file d'attente sera exécutée uniformément au prochain Tick (ou à la phase de microtâche du Tick actuel). Dans le même temps, les Watchers avec le même ID ne seront pas ajoutés à la file d'attente à plusieurs reprises, donc l'exécution du Watcher ne sera pas exécutée 1 000 fois. La mise à jour finale de la vue ne fera que modifier directement le DOM correspondant au test de 0 à 1000. Il est garanti que l'action de mise à jour de la vue pour faire fonctionner le DOM est appelée au prochain Tick (ou à la phase microtâche du Tick actuel) après l'exécution de la pile actuelle, ce qui optimise grandement les performances.

Question intéressante

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')
   })
 }
})
Tout le monde doit connaître l'ordre d'exécution de ceci et l'imprimer dans l'ordre : 1, promesse, 2, 3.

  1. Parce que this.msg = 'end' est déclenché en premier, la mise à jour de l'observateur est déclenchée, poussant ainsi le rappel de l'opération de mise à jour dans la file d'attente des événements vue.

  2. this.$nextTick entre également dans une nouvelle fonction de rappel pour le push de la file d'attente d'événements. Ils passent tous par setImmediate --> MessageChannel --> Promise.resolve().then est une microTâche, elle imprimera donc la promesse en premier.

  3. Lorsque MessageChannel et setImmediate sont pris en charge, leur ordre d'exécution est prioritaire sur setTimeout (dans IE11/Edge, le délai setImmediate peut être inférieur à 1 ms, tandis que setTimeout a un délai minimum de 4 ms, donc setImmediate exécute le fonction de rappel plus tôt que setTimeout(0) Deuxièmement, parce que le tableau de rappel est reçu en premier dans la file d'attente des événements), 2 sera imprimé, puis 3

  4. . être imprimé. Dans le cas où MessageChannel et setImmediate ne sont pas pris en charge, timeFunc sera défini via Promise, et l'ancienne version de Vue avant 2.4 exécutera la promesse en premier. Cette situation fera que l'ordre deviendra : 1, 2, promesse, 3. Étant donné que this.msg doit d'abord déclencher la fonction de mise à jour dom, la fonction de mise à jour dom sera d'abord collectée par le rappel dans la file d'attente temporelle asynchrone, puis Promise.resolve().then(function () { console.log('promise! ')} sera défini. ) une telle microTâche, puis la définition de $nextTick sera collectée par le rappel. Nous savons que la file d'attente satisfait au principe du premier entré, premier sorti, donc les objets collectés par le rappel sont exécutés en premier.

Postscript

Si vous êtes intéressé par le code source de Vue, vous pouvez venir ici : Explications plus intéressantes du code source de la convention Vue

Je pense que vous maîtrisez la méthode après avoir lu le cas dans cet article. Pour des informations plus intéressantes, veuillez prêter attention aux autres articles connexes sur le site Web chinois de php !

Lecture recommandée :

JS implémente la fonction de dégradé de transparence

Parcours jQuery des étapes de mise en œuvre des nœuds et des attributs XML

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn