Use of 1 nextTick
The image of dom in vue is not real-time. When the data changes, vue adds the rendered watcher to the asynchronous queue, asynchronous execution, and unified modification of dom after the execution of synchronous code. Let's see the following code.
<template> <div class="box">{{msg}}</div> </template> export default { name: 'index', data () { return { msg: 'hello' } }, mounted () { this.msg = 'world' let box = document.getElementsByClassName('box')[0] console.log(box.innerHTML) // hello } }
As you can see, dom is not updated immediately after data modification. dom updates are asynchronous and cannot be retrieved by synchronous code. nextTick is needed to be retrieved in the next event loop.
this.msg = 'world' let box = document.getElementsByClassName('box')[0] this.$nextTick(() => { console.log(box.innerHTML) // world })
If we need to get the updated dom information, such as dynamic acquisition of width and height, location information, etc., we need to use nextTick.
Principle Analysis of dom Update and nextTick for Data Change
2.1 Data changes
vue bidirectional data binding relies on ES5's Object.defineProperty. When data is initialized, getter and setter are created for each attribute through Object.defineProperty, which turns data into responsive data. When modifying attribute values, such as this.msg = world, setter is actually triggered. Look at the source code below, in order to facilitate the more read, the source code has been deleted.
Data change trigger set function
Object.defineProperty(obj, key, { enumerable: true, configurable: true, // Triggering set function after data modification completes dom update through a series of operations set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() // Execute dep notify method } })
Execute dep.notify method
export default class Dep { constructor () { this.id = uid++ this.subs = [] } notify () { const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { // In fact, traversal executes the update method for elements in subs arrays subs[i].update() } } }
When data is referenced, such as <div>{msg} </div>, get method is executed, rendering Watcher is added to subs array, and updating method of Watcher is executed when data is changed.
update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) //Execute queueWatcher } }
The update method finally executes queueWatcher
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) { // Ensure that nextTick is executed only once through waiting waiting = true // The queueWatcher method eventually passes the flush Scheduler Queue into nextTick for execution. nextTick(flushSchedulerQueue) } } }
Execute the flush SchedulerQueue method
function flushSchedulerQueue () { currentFlushTimestamp = getNow() flushing = true let watcher, id ... for (index = 0; index < queue.length; index++) { watcher = queue[index] if (watcher.before) { watcher.before() } id = watcher.id has[id] = null // Traversing the run method of rendering watcher to complete view updating watcher.run() } // Reset waiting variable resetSchedulerState() ... }
That is to say, when data changes eventually pass flush Scheduler Queue into nextTick to execute the flush Scheduler Queue function, the watcher.run() method will traverse the execution of the watcher.run() method, and the watcher.run() method will eventually complete the view update. Next, let's see what the key nextTick method is.
2.2 nextTick
The nextTick method is push ed into the callbacks array by the incoming callback, and then the timerFunc method is executed.
export function nextTick (cb?: Function, ctx?: Object) { let _resolve // push into callbacks array callbacks.push(() => { cb.call(ctx) }) if (!pending) { pending = true // Execute the timerFunc method timerFunc() } }
timerFunc
let timerFunc // Determine whether native support for Promise is available if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { // If native support for Promise implements flush Callbacks with Promise p.then(flushCallbacks) if (isIOS) setTimeout(noop) } isUsingMicroTask = true // Determine whether native support for Mutation Observer is available } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) { let counter = 1 // If native support for Mutation Observer implements flush Callbacks with Mutation Observer 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 // Determine whether setImmediate is naturally supported } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { timerFunc = () => { // If setImmediate is natively supported, flush Callbacks are executed with setImmediate setImmediate(flushCallbacks) } // Use setTimeout 0 without support } else { timerFunc = () => { // Using setTimeout to execute flush Callbacks setTimeout(flushCallbacks, 0) } } // flushCallbacks ultimately executes the callback function passed in by the nextTick method function flushCallbacks () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }
nextTick will use microTask first, followed by macroTask.
That is to say, tasks in nextTick actually execute asynchronously, and nextTick(callback) is similar to
Promise.resolve().then(callback), or setTimeout(callback, 0).
That is to say, vue's view update nextTick (flush Scheduler Queue) is equivalent to setTimeout (flush Scheduler Queue, 0), which asynchronously executes the flush Scheduler Queue function, so we do not update dom immediately at this.msg = hello.
To read the dom information after the dom update, we need to create an asynchronous task after the asynchronous task is created.
To validate this idea, we don't need nextTick to experiment with setTimeout directly. The following code validates our idea.
<template> <div class="box">{{msg}}</div> </template> <script> export default { name: 'index', data () { return { msg: 'hello' } }, mounted () { this.msg = 'world' let box = document.getElementsByClassName('box')[0] setTimeout(() => { console.log(box.innerHTML) // world }) } }
If we nextTick before data modification, the asynchronous tasks we add will be executed before the rendered asynchronous tasks, and the updated dom will not be available.
<template> <div class="box">{{msg}}</div> </template> <script> export default { name: 'index', data () { return { msg: 'hello' } }, mounted () { this.$nextTick(() => { console.log(box.innerHTML) // hello }) this.msg = 'world' let box = document.getElementsByClassName('box')[0] } }
3 Summary
In order to ensure performance, vue adds DOM modifications to asynchronous tasks. After all synchronous codes are executed, DOM modifications are unified. Multiple data modifications in an event cycle only trigger watcher.run(). That is, through nextTick, nextTick gives priority to creating asynchronous tasks using microTask. If you need to get the modified DOM information in the vue project, you need to create an asynchronous task after the DOM update task through nextTick. As stated on the official website, nextTick will perform a delayed callback after the next DOM update cycle ends.
Reference Articles
ps: Welcome to pay attention to the Wechat Public Number, Front-end Roaming Guide, which has just been launched. It will regularly publish high-quality original articles and translations. Thank you.~