Firstly, this paper is based on Vue 2.6.8
剛開始接觸Vue的時候,哇nextTick好強,咋就在這裏面寫就是dom更新以後,當時連什麼macrotask、microtask都不知道(若是你也不是很清楚,推薦 點這裏去看一下,也有助於你更好地理解本文),再後來,寫的多了看得多了愈發膨脹了,就想看看這個nextTick究竟是咋實現的
關於vue中nextTick方法的實現位於vue源碼下的src/core/util/next-tick.js,(下文所提到的next-tick.js都是指這個文件)因爲篇幅緣由就不全粘過來了,下面隨着分析會貼出主要代碼片斷。vue
將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') }
運行結果以下 沒有任何問題(注意測試要使用Vue.nextTick而不是$nextTick,由於上文講到過$nextTick函數的ctx參數是當前組件)
3) 函數體內只剩下中間if (!pending)
,這段代碼很好懂,pending明顯是一個狀態位,而timerFunc()
就應該是nextTick實現異步的核心了
函數
在代碼中搜索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
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的猜想,不過這兩個問題不影響總體邏輯的理解就是了