關於Vue中nextTick()的思考

  我的項目中有一個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對象,網上的資料好像也不多,如果讀者有推薦的資料,請在評論中告訴我~
  以上是我的一些總結,希望可以幫到遇到同樣問題的萌新。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章