Maison >interface Web >js tutoriel >Explication détaillée de l'utilisation du mécanisme Vue nextTick
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'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 !== '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) } } 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 ++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.
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
. ê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!