詳細分析Vue.nextTick()實現

Firstly, this paper is based on Vue 2.6.8
剛開始接觸Vue的時候,哇nextTick好強,咋就在這裏面寫就是dom更新以後,當時連什麼macrotask、microtask都不知道(若是你也不是很清楚,推薦 點這裏去看一下,也有助於你更好地理解本文),再後來,寫的多了看得多了愈發膨脹了,就想看看這個nextTick究竟是咋實現的

1、源碼

       關於vue中nextTick方法的實現位於vue源碼下的src/core/util/next-tick.js,(下文所提到的next-tick.js都是指這個文件)因爲篇幅緣由就不全粘過來了,下面隨着分析會貼出主要代碼片斷。vue

2、分析

  1. 函數定義

    將nextTick定義到Vue原型鏈上代碼位於src/core/instance/render.js,代碼以下數組

    Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
     }

    上述代碼中return的nextTick就是咱們本文主角,他的定義以下babel

    export function nextTick (cb?: Function, ctx?: Object) { // next-tick.js line87
      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()
      }
      if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
          _resolve = resolve
        })
      }
    }

    1)函數參數cb?: Function,意味參數cb(callback)的類型爲函數或undefined,ctx(context)同理,這種類型判斷是TypeScript的寫法。
          同時能夠看到咱們經常使用的this.$nextTick()已經 在定義到原型鏈上時 給nextTick函數傳了ctx參數也就是指向當前組件的this。(這句話好繞,暴露了本身的語文水平)
    2)函數體內callbacks是在next-tick.js line10定義的一個數組,判斷若是參數cb不爲undefined,就把cb push到callbacks中,若是cb爲undefined,則把_resolve(ctx)push到callbacks中。
          由於平時使用都是傳回調的,因此很好奇cb什麼狀況下會爲undefined,去翻看Vue官方文檔發現:dom

    2.1.0 起新增:若是沒有提供回調且在支持 Promise 的環境中,則返回一個 Promise。

          這就對了,函數體內最後的if判斷很明顯就是這個意思if (!cb && typeof Promise !== 'undefined')沒有回調&&支持Promise。
          在測試工程裏寫以下代碼異步

    created() {
      Vue.nextTick(undefined, { a: 'in nextTick' }).then(ctx => {
        console.log(ctx.a)
      })
      console.log('out nextTick')
    }

          運行結果以下nextTick().then()      沒有任何問題(注意測試要使用Vue.nextTick而不是$nextTick,由於上文講到過$nextTick函數的ctx參數是當前組件)
    3) 函數體內只剩下中間if (!pending),這段代碼很好懂,pending明顯是一個狀態位,而timerFunc()就應該是nextTick實現異步的核心了
    函數


  2. timerFunc

          在代碼中搜索timerFunc發現除了聲明和nextTick函數中,還有四處使用timerFunc的代碼片斷,這四處代碼片斷被嵌套在一個大的if else判斷裏測試

    if (typeof Promise !== 'undefined' && isNative(Promise)) { // next-tick.js line42
        timerFunc = ...
      } else if (!isIE && typeof MutationObserver !== 'undefined' && (
        isNative(MutationObserver) ||
        MutationObserver.toString() === '[object MutationObserverConstructor]'
      )) {
        timerFunc = ...
      } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
        timerFunc = ...
      } else {
        timerFunc = ...
      }

          能夠看到nextTick優先使用microTask(Promise和MutationObserver)而後使用macroTask(setImmediate和setTimeout)這也符合尤大在2.6.0的更新日誌中說的this

    next-tick: revert nextTick to alaways use microtask

          首先這個alaways是否是拼錯了
          不太對啊,我是否是對always有什麼誤解,這不是明明還用macroTask的可能嗎
          至於具體在不一樣的異步方式中是如何定義timerFunc的大同小異,若是仔細講解的話還要花費部分篇幅說明MutationObserver,畢竟咱們的主角是nextTick,這裏就不喧賓奪主,反正都是把timerFunc定義爲在異步中調用flushCallbacks的函數,而函數flushCallbacks的定義在源碼中以下spa

    function flushCallbacks () { // next-tick.js line13
      pending = false
      const copies = callbacks.slice(0)
      callbacks.length = 0
      for (let i = 0; i < copies.length; i++) {
        copies[i]()
      }
    }

           由於callbacks裏都是函數,因此一層深拷貝的方式就能夠知足複製需求,定義一個copies數組等於callbacks,而後清空callbacks,而後遍歷copies數組調用其中的函數。prototype

3、總結

      nextTick實現流程大體是這樣的:

st=>operation: 將回調函數push到callbacks數組中
op=>operation: 調用timerFunc()根據不一樣狀況採用不一樣異步方式調用flushCallbacks
e=>operation: 刷新callbacks數組,遍歷並調用其中全部函數
st->op->e

      能看懂一部分Vue源碼對於我這種入行不到一年的萌新仍是很是有成就感的一件事,可是還有兩個地方有些疑慮,上文也都有說起:

1) 箭頭函數不能改變this指向爲何使用nextTick的時候可使用箭頭函數,即nextTick函數定義中的cb.call(ctx)
2) 爲何更新日誌中寫的是always use microtask,而我找到的源碼中是存在使用macroTask的狀況的
      後續將對問題的解決進行補充,也歡迎大佬在線評論傳道授業


2019/4/14      對於以前的兩個問題,我只能作出Vue自己使用了一部分babel的猜想,不過這兩個問題不影響總體邏輯的理解就是了

相關文章
相關標籤/搜索