詳解Vue中的$nextTick

當在代碼中更新了數據,並但願等到對應的Dom更新以後,再執行一些邏輯。這時,咱們就會用到$nextTickvue

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

官方文檔對nextTick的解釋是:數組

在下次 DOM 更新循環結束以後執行延遲迴調。在修改數據以後當即使用這個方法,獲取更新後的 DOM。

那麼,Vue是如何作的這一點的,是否是在調用修改Dom的Api以後(appendChild, textContent = "xxxxx" 諸如此類),調用了咱們的回調函數?
實際上發生了什麼呢。瀏覽器

源碼

nextTick的實現邏輯在這個文件裏:app

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
    })
  }
}

能夠看到this

  1. 回調函數被存放到了一個數組裏:callbacks。
  2. 若是沒有傳遞迴調函數,這個方法會返回一個Promise,而後吧reslove當成回調函數放到flushCallbacks中。因此文檔解釋了把本該當成回調函數的callbacks放到then裏的用法。
  3. 而後,有一個變量叫pending,若是不在pending中,則執行函數timerFunc。並且pending默認等於false。
  4. flushCallbacks這個函數會一口氣執行全部回調函數。

timerFunc

timerFunc定義在這裏線程

能夠看到timerFunc是在一個已resolve了的Promise的then 中執行了flushCallbacks.code

利用了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更新以後
相關文章
相關標籤/搜索