This time I will show you how to use Vue nextTick and what are the precautions for using Vue nextTick. The following is a practical case, let's take a look.
export default { data () { return { msg: 0 } }, mounted () { this.msg = 1 this.msg = 2 this.msg = 3 }, watch: { msg () { console.log(this.msg) } } }
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) } } ...
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) } } }
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() ... } }
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 }) } } })()
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) } }
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 beInteresting 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') }) } })
When MessageChannel and setImmediate are supported, their execution order takes precedence over setTimeout (in IE11/Edge, 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 2 will be printed, and then 3
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.
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:
How to operate JS to realize transparency gradient animation
##How to operate JS to realize simple folding and unfolding animation
The above is the detailed content of How to use Vue nextTick. For more information, please follow other related articles on the PHP Chinese website!