深入 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 會把所有回調統一放進一個數組,然後用最快且兼容的方式調度:
-
如果環境支持 Promise,優先用
Promise.resolve().then()把回調塞進微任務隊列。這是現代瀏覽器最常用、延遲最低的手段。
-
如果 Promise 不可用,退而求其次,看是否支持 MutationObserver。
創建一個文本節點並監聽它的變動,當節點改動時觸發回調,同樣屬於微任務。
-
如果上述兩者都不支持,Vue 會繼續降級:
- 在 IE 裏嘗試 setImmediate,它介於宏任務與微任務之間,比 setTimeout 快;
- 最後兜底 setTimeout(fn, 0),把回調放到宏任務隊列末尾。
整個流程可以概括為一句話:
“能用微任務就不用宏任務,能用更快的宏任務就不用 setTimeout。”
這樣既保證了回調儘早執行,又兼顧了老瀏覽器的兼容性。
總結
- 作用:等本輪 DOM 批量更新完再跑回調。
- 原理:先找微任務,再找宏任務,按優先級把回調塞進最合適的異步隊列。