我的項目中有一個swiper插件,在vue實例created(生命週期相關)函數中,先用ajax異步加載數據,再初始化swiper輪播插件時,遇到了一個問題,由於動態數據加載導致了swiper初始化後會滑動到最後一個item。我當時的解決方法是用setTimeout()來延遲初始化,之後在學習es6的時候,我發現更好的解決辦法是使用promise.then.但是,沒有最後只有更好,promise.then可能會有的兼容性問題,可以用Vue自帶的nextTick()方法來解決。
nextTick()方法的官方說明是這樣的:
在下次 DOM 更新循環結束之後執行延遲迴調。在修改數據之後立即使用這個方法,獲取更新後的 DOM。
看起來非常適合解決我的問題,那nextTick()方法到底是怎樣實現的呢,是否包括我之前思考過的兩種解決方案呢,帶着這樣的思考我翻看了一下nextTick的源碼:Vue—nextTick
我在看源碼之前有看過一些解析nextTick的文章,他們一般描述nextTick使用了優雅降級的方式,內部其實是以下三種方式回調:
1. promise.then
2. Mutation.Observe
3. setTimeout(fn,0)
但是當我翻看源碼時,發現更新後的源碼是這樣定義nextTick的:
1. microtasks function
2. macrotasks function
如果你對這兩個名詞感到困惑,可以讀讀這個
官方源碼中的解釋是這樣的:
Here we have async deferring wrappers using both micro and macro tasks.
In < 2.4 we used micro tasks everywhere, but there are some scenarios where micro tasks have too high a priority and fires in between supposedly sequential events (e.g. #4521, #6690) or even between bubbling of the same event (#6566). However, using macro tasks everywhere also has subtle problems when state is changed right before repaint (e.g. #6813, out-in transitions).
Here we use micro task by default, but expose a way to force macro task when needed (e.g. in event handlers attached by v-on).
簡單來講,就是以前(<2.4版本)我們總是使用microtask,不過他的優先級太高會導致一些問題(比如插入一些順序執行的事件處理之中),但是完全使用macrotask也有一些麻煩,所以我們現在默認使用microtask,在需要的時候再強制使用macrotask。爲什麼默認使用microtask呢,如下:
根據 HTML Standard,在每個 task 運行完以後,UI 都會重渲染,那麼在 microtask 中就完成數據更新,當前task 結束就可以得到最新的 UI 了。反之如果新建一個 task 來做數據更新,那麼渲染就會進行兩次。
理解了這些知識後我們來詳細的看源碼。
源碼中microtask主要是使用promise.then
// Determine MicroTask defer implementation.
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
microTimerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
} else {
microTimerFunc = macroTimerFunc
}
macrotask則比較複雜,按照順序依次是setImmediate->MessageChannel->setTimeout(fn,0)
// Determine (macro) Task defer implementation.
// Technically setImmediate should be the ideal choice, but it's only available
// in IE. The only polyfill that consistently queues the callback after all DOM
// events triggered in the same loop is by using MessageChannel.
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
macroTimerFunc = () => {
setImmediate(flushCallbacks)
}
} else if (typeof MessageChannel !== 'undefined' && (
isNative(MessageChannel) ||
// PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = () => {
port.postMessage(1)
}
} else {
macroTimerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
註釋說的很明確,技術上setImmediate()應該是理想的選擇,但它只能在IE中使用。在同一循環中觸發的所有DOM事件之後,唯一一個對回調進行排隊的polyfill是使用MessageChannel。這裏給MessageChannel對象port1設置事件處理,再利用port2給port1傳遞消息來調用。注意,setTimeout(fn,0)即使第二個參數設爲0,仍然會有最少4ms的延遲。(我目前還沒有使用過MessageChannel對象,網上的資料好像也不多,如果讀者有推薦的資料,請在評論中告訴我~
以上是我的一些總結,希望可以幫到遇到同樣問題的萌新。