關於js的event loop知識點,可閱讀JavaScript併發模型與Event Loop,這裡就不著重介紹。
在此,再推薦一篇關於event loop的文章Tasks, microtasks, queues and schedules。
好了,在對js的event loop有一定了解後,我們就來進入今天的主題,關於vue的nextTick。
在vue中,資料監測都是通過Object.defineProperty來重寫裡面的set和get方法實現的,vue更新DOM是非同步的,每當觀察到資料變化時,vue就開始一個佇列,將同一事件迴圈內所有的資料變化快取起來,等到下一次event loop,將會把佇列清空,進行dom更新,內部使用的microtask MutationObserver來實現的。
雖然資料驅動建議避免直接操作dom,但有時也不得不需要這樣的操作,這時就該Vue.nextTick(callback)
出場了,它接受一個回撥函式,在dom更新完成後,這個回撥函式就會被呼叫。不管是vue.nextTick還是vue.prototype.$nextTick都是直接用的nextTick這個閉包函式。
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]()
}
}
//other code
})()複製程式碼
callbacks就是快取的所有回撥函式,nextTickHandler就是實際呼叫回撥函式的地方。
if (typeof Promise !== `undefined` && isNative(Promise)) {
var p = Promise.resolve()
var logError = err => { console.error(err) }
timerFunc = () => {
p.then(nextTickHandler).catch(logError)
if (isIOS) setTimeout(noop)
}
} else if (typeof MutationObserver !== `undefined` && (
isNative(MutationObserver) ||
MutationObserver.toString() === `[object MutationObserverConstructor]`
)) {
var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
} else {
timeFunc = () => {
setTimeout(nextTickHandle, 0)
}
}複製程式碼
為讓這個回撥函式延遲執行,vue優先用promise來實現,其次是html5的MutationObserver,然後是setTimeout。前兩者屬於microtask,後一個屬於macrotask。下面來看最後一部分
return function queueNextTick(cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) cb.call(ctx)
if (_resolve) _resolve(ctx)
})
if (!pending) {
pending = true
timerFunc()
}
if (!cb && typeof Promise !== `undefined`) {
return new Promise(resolve => {
_resolve = resolve
})
}
}複製程式碼
這就是我們真正呼叫的nextTick函式,在一個event loop內它會將呼叫nextTick的cb回撥函式都放入callbacks中,pending用於判斷是否有佇列正在執行回撥,例如有可能在nextTick中還有一個nextTick,此時就應該屬於下一個迴圈了。最後幾行程式碼是promise化,可以將nextTick按照promise方式去書寫(暫且用的較少)。
nextTick就這麼多行程式碼,但從程式碼裡面可以更加充分的去理解event loop機制。