從event loop看vue的nextTick

網易考拉前端團隊發表於2019-03-03

關於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機制。

參考資料

vue

JavaScript併發模型與Event Loop

Tasks microtasks queues and schedules

MutationObserver

相關文章