從event loop看vue的nextTick

關於js的event loop知識點,可閱讀JavaScript併發模型與Event Loop,這裏就不着重介紹。
在此,再推薦一篇關於event loop的文章Tasks, microtasks, queues and scheduleshtml

好了,在對js的event loop有必定了解後,咱們就來進入今天的主題,關於vue的nextTick。vue

在vue中,數據監測都是經過Object.defineProperty來重寫裏面的set和get方法實現的,vue更新DOM是異步的,每當觀察到數據變化時,vue就開始一個隊列,將同一事件循環內全部的數據變化緩存起來,等到下一次event loop,將會把隊列清空,進行dom更新,內部使用的microtask MutationObserver來實現的。html5

雖然數據驅動建議避免直接操做dom,但有時也不得不須要這樣的操做,這時就該Vue.nextTick(callback)出場了,它接受一個回調函數,在dom更新完成後,這個回調函數就會被調用。無論是vue.nextTick仍是vue.prototype.$nextTick都是直接用的nextTick這個閉包函數。git

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就是實際調用回調函數的地方。github

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。下面來看最後一部分promise

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機制。bash

參考資料

vue閉包

JavaScript併發模型與Event Loop併發

Tasks microtasks queues and schedules

MutationObserver

相關文章
相關標籤/搜索