全面解析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 }) } }
可以看到
- 回撥函式被存放到了一個數組裡:callbacks。
- 如果沒有傳遞迴調函式,這個方法會返回一個Promise,然後吧reslove當成回撥函式放到flushCallbacks中。所以文件解釋了把本該當成回撥函式的callbacks放到then裡的用法。
- 然後,有一個變數叫pending,如果不在pending中,則執行函式timerFunc。而且pending預設等於false。
- 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執行緒。
總結
- 首先timerFunc函式負責把回撥函式們都丟到事件迴圈的隊尾
- 然後,nextTick函式負責把回撥函式們都儲存起來。
- 呼叫nextTick函式時會呼叫timerFunc函式
- Vue更新Dom也會使用nextTick,而且在開發者呼叫nextTick之前。
- 因為4中的先後關係和事件迴圈的佇列性質,確保了開發者的nextTick的回撥一定在Dom更新之後
以上就是解析Vue中的$nextTick的詳細內容,更多關於Vue中的$nextTick的資料請關注我們其它相關文章!