1. 程式人生 > 程式設計 >全面解析Vue中的$nextTick

全面解析Vue中的$nextTick

當在程式碼中更新了資料,並希望等到對應的Dom更新之後,再執行一些邏輯。這時,我們就會用到$nextTick

funcion callback(){
 //等待Dom更新,然後搞點事。
}
$nextTick(callback);

官方文件對nextTick的解釋是:

在下次 DOM 更新迴圈結束之後執行延遲迴調。在修改資料之後立即使用這個方法,獲取更新後的 DOM。

那麼,Vue是如何做的這一點的,是不是在呼叫修改Dom的Api之後(appendChild,textContent = "xxxxx" 諸如此類),呼叫了我們的回撥函式?
實際上發生了什麼呢。

原始碼

nextTick的實現邏輯在這個檔案裡:

vue/src/core/util/next-tick.js

我們呼叫的this.$nextTick實際上是這個方法:

export function nextTick (cb?: Function,ctx?: Object) {
 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()
 }
 // $flow-disable-line
 if (!cb && typeof Promise !== 'undefined') {
  return new Promise(resolve => {
   _resolve = resolve
  })
 }
}

可以看到

  1. 回撥函式被存放到了一個數組裡:callbacks。
  2. 如果沒有傳遞迴調函式,這個方法會返回一個Promise,然後吧reslove當成回撥函式放到flushCallbacks中。所以文件解釋了把本該當成回撥函式的callbacks放到then裡的用法。
  3. 然後,有一個變數叫pending,如果不在pending中,則執行函式timerFunc。而且pending預設等於false。
  4. flushCallbacks這個函式會一口氣執行所有回撥函式。

timerFunc

timerFunc定義在這裡

可以看到timerFunc是在一個已resolve了的Promise的then 中執行了flushCallbacks.

利用了js事件迴圈的微任務的機制

所以,每當我們呼叫$nextTick,如果pending為false,就會呼叫timerFunc,然後timerFunc會把flushCallbacks給塞到事件迴圈的隊尾,等待被呼叫。

if (typeof Promise !== 'undefined' && isNative(Promise)) {
 const p = Promise.resolve()
 timerFunc = () => {
  p.then(flushCallbacks)
 }
}

flushCallbacks

然後在這個檔案裡還有一個函式叫:flushCallbacks
用來把儲存的回撥函式給全執行並清空。

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

pending

什麼時候pending為true呢?

從timerFunc被呼叫到flushCallbacks被呼叫期間pending為true

即一個事件迴圈週期

在pending期間加入的回撥函式,會被已經等待執行的flushCallbacks函式給執行。

核心機制

看完原始碼,發現除了利用了一個微任務的機制,和Dom更新一點關係都沒有哇。

其實呼叫nextTick的不僅是開發者,Vue更新Dom時,也用到了nextTick。

開發者更新繫結的資料之後,Vue就會立刻呼叫nextTick,把更新Dom的回撥函式作為微任務塞到事件迴圈裡去。

於是,在微任務佇列中,開發者呼叫的nextTick的回撥函式,就一定在更行Dom的回撥函式之後執行了。

但是問題又來了,根據瀏覽器的渲染機制,渲染執行緒是在微任務執行完成之後執行的。渲染執行緒沒執行,怎麼拿到Dom呢?

因為,渲染執行緒只是把Dom樹渲染成UI而已,Vue更新Dom之後,在Dom樹裡,新的Dom節點已經存在了,js執行緒就已經可以拿到新的Dom了。除非開發者讀取Dom的計算屬性,觸發了強制重流渲染執行緒才會打斷js執行緒。

總結

  1. 首先timerFunc函式負責把回撥函式們都丟到事件迴圈的隊尾
  2. 然後,nextTick函式負責把回撥函式們都儲存起來。
  3. 呼叫nextTick函式時會呼叫timerFunc函式
  4. Vue更新Dom也會使用nextTick,而且在開發者呼叫nextTick之前。
  5. 因為4中的先後關係和事件迴圈的佇列性質,確保了開發者的nextTick的回撥一定在Dom更新之後

以上就是解析Vue中的$nextTick的詳細內容,更多關於Vue中的$nextTick的資料請關注我們其它相關文章!