深入 Vue 的 nextTick

在初學 Vue 時,我們經常會遇到這樣的困惑:數據明明已經改了,可頁面卻沒立刻變化;或者手動去獲取更新後的 DOM,結果拿到的是舊值。這時候,Vue 官方會推薦我們使用 this.$nextTick()。它到底做了什麼?為什麼“等一下”就能解決問題?本文嘗試用通俗的語言把它的作用和實現原理講清楚。

一、nextTick 的作用

Vue 的響應式系統有一個核心設計:異步批量更新。

當你連續修改多條數據時,Vue 不會每改一次就立刻重繪頁面,而是把這些變更緩存起來,等到本輪事件循環結束後再統一 patch DOM。

這樣做的好處顯而易見:

  • 減少不必要的重排重繪,性能更好;
  • 避免同一事件循環裏多次操作 DOM 帶來的閃爍或抖動。

但副作用也隨之而來:

在數據改變後的同一輪同步代碼裏,DOM 仍然是舊的。

因此,如果你需要在視圖真正更新完成後執行某些邏輯(比如獲取元素新尺寸、手動聚焦、調用依賴於 DOM 的第三方庫),就必須等下一輪。

nextTick它的回調會在當前批量更新結束後、瀏覽器重繪之前被調用。

舉個最簡單的例子:

this.msg = 'Hello'
console.log(this.$el.textContent) // 舊值
this.$nextTick(() => {
  console.log(this.$el.textContent) // 新值
})

二、nextTick 的實現原理

瀏覽器事件循環裏有兩類異步隊列:

  • 微任務(micro-task):Promise、MutationObserver;執行時機緊跟着當前棧清空後。
  • 宏任務(macro-task):setTimeout、setImmediate、MessageChannel;下一輪事件循環執行。

Vue 的源碼中,nextTick 會把所有回調統一放進一個數組,然後用最快且兼容的方式調度:

  1. 如果環境支持 Promise,優先用 Promise.resolve().then() 把回調塞進微任務隊列。

    這是現代瀏覽器最常用、延遲最低的手段。

  2. 如果 Promise 不可用,退而求其次,看是否支持 MutationObserver。

    創建一個文本節點並監聽它的變動,當節點改動時觸發回調,同樣屬於微任務。

  3. 如果上述兩者都不支持,Vue 會繼續降級:

    • 在 IE 裏嘗試 setImmediate,它介於宏任務與微任務之間,比 setTimeout 快;
    • 最後兜底 setTimeout(fn, 0),把回調放到宏任務隊列末尾。

整個流程可以概括為一句話:

“能用微任務就不用宏任務,能用更快的宏任務就不用 setTimeout。”

這樣既保證了回調儘早執行,又兼顧了老瀏覽器的兼容性。

總結

  • 作用:等本輪 DOM 批量更新完再跑回調。
  • 原理:先找微任務,再找宏任務,按優先級把回調塞進最合適的異步隊列。