這一篇主要講講
nextTick
源碼,看看該方法的實現,以及爲什麼能在這個方法裏保證拿到DOM
節點。vue
nextTick
方法在./src/core/util/next-tick.js
,下面爲部分源碼展現:promise
nextTick
方法接受兩個入參,分別是回調方法cb
和上下文ctx
;cb
參數都會往隊列推入一個函數,後續任務隊列根據cb
參數判斷是否調用cb
或者是否執行_resolve(ctx)
修改promise
狀態;pending
狀態是否執行任務promise
export function nextTick (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 => {
_resolve = resolve
})
}
}
複製代碼
先來講說調用nextTick
的返回值,由於返回值是一個promise
,因此咱們能夠使用then
的寫法或者async/await
的寫法,加上使用cb
的寫法,存在三種寫法。bash
this.$nextTick(function() {
// do something
})
or
this.$nextTick().then((ctx)=> {
// do something
})
or
await this.$nextTick()
// do something
複製代碼
接下來則是nextTick
裏比較重要的方法timerFunc
的實現:框架
Promise
;MutationObserver
;setImmediate
;setTimeout
;if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// In problematic UIWebViews, Promise.then doesn't completely break, but // it can get stuck in a weird state where callbacks are pushed into the // microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// Use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
let counter = 1
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
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
// Techinically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
複製代碼
從代碼中isUsingMicroTask
中能夠看到只有Promise
、MutationObserver
屬於微任務,另外兩個則屬於宏任務;看到該方法的實現咱們就能夠知道爲何在nextTick
方法中能保證拿到DOM
。async
兩種場景的解釋:函數
vue
第一次初始化的時候,咱們在beforeCreated
和created
生命週期裏想要使用DOM
則必須使用nextTick
,這是由於初始化的過程屬於宏任務,整個函數調用棧未清空,nextTick
的回調屬於微任務,因此nextTick
的回調必須在整個初始化結束後纔會執行。data
數據後,又如何保證獲取修改後的數據DOM
?修改data
數據其實是觸發組件實例的watcher
執行update
更新,而在update
裏面又執行了queueWatcher
,下面👇則是queueWatcher
方法的代碼,在代碼裏面咱們能夠看到最後實際上也是調用nextTick(flushSchedulerQueue)
。所以,想獲取data
修改後的DOM
,調用nextTick
能保證這種任務執行的順序。瞭解watcher
能夠看這篇juejin.im/post/5d181b…。oop
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
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
}
複製代碼
其實queueWatcher
方法裏面的邏輯還告訴了咱們另一個框架知識點:post
爲何咱們同時修改多個data屬性,不會屢次更新視圖?ui
在update
方法裏,由於最後實際上調用nextTick
執行微任務去更新視圖,瞭解過event loop
機制的應該知道,必須等待當前宏任務的調用棧清空纔去執行微任務,這也就是爲何當咱們同時修改多個data
屬性時候,該判斷if (has[id] == null)
防止重複添加更新任務,而且利用了event loop
機制在合適的時機去更新視圖。this