溫故而知新,淺析 Vue nextTick 原理 |8月更文挑戰

這是我參與8月更文挑戰的第3天,活動詳情查看:8月更文挑戰 html

nextTick 是什麼?

nextTick 本質就是執行延遲迴調的鉤子,接受一個回調函數做爲參數,在下次 DOM 更新循環結束以後執行延遲迴調。在修改數據以後當即使用這個方法,獲取更新後的 DOM。Vue 2.1.0 開始,若是沒有提供回調函數,且在支持 Promise 的環境中,則返回一個 Promise 。注意 Vue 自己是不自帶 polyfill 的,若是環境不支持 Promise ,則須要本身提供 polyfill。vue

nextTick 的做用

提及 nextTick ,也不得不說說 Vue 的異步更新,Vue 在更新 DOM 時是異步執行的。只要偵聽到數據變化,Vue 將開啓一個隊列,並緩衝在同一事件循環中發生的全部數據變動。若是同一個 watcher 被屢次觸發,只會被推入到隊列中一次。這種在緩衝時去除重複數據對於避免沒必要要的計算和 DOM 操做是很是重要的。而後,在下一個的事件循環「tick」中,Vue 刷新隊列並執行實際 (已去重的) 工做。Vue(2.6.x) 在內部對異步隊列嘗試使用原生的 Promise.thenMutationObserversetImmediate,若是執行環境不支持,則會採用 setTimeout(fn, 0) 代替。git

例如,當你設置 vm.someData = 'new value',該組件不會當即從新渲染。當刷新隊列時,組件會在下一個事件循環「tick」中更新。多數狀況咱們不須要關心這個過程,可是若是你想基於更新後的 DOM 狀態來作點什麼,這就可能會有些棘手。雖然 Vue.js 一般鼓勵開發人員使用「數據驅動」的方式思考,避免直接接觸 DOM,可是有時咱們必需要這麼作。爲了在數據變化以後等待 Vue 完成更新 DOM,能夠在數據變化以後當即使用 Vue.nextTick(callback) 。這樣回調函數將在 DOM 更新完成後被調用github

nextTick 除了讓咱們能夠在 DOM 更新以後執行延遲迴調,還有一個做用就是 Vue 內部 使用nextTick,把渲染 Dom 操做這個操做 放入到 callbacks 中。web

nextTick 爲何是 next tick?

從字面意思理解,next 下一個,tick 滴答(鐘錶)來源於定時器的週期性中斷(輸出脈衝),一次中斷表示一個 tick,也被稱作一個「時鐘滴答」,nextTick 顧名思義就是下一個時鐘滴答,下一個任務。下一個任務,在 Event Loop 中在熟悉不過了經過一個例子簡單回憶一下 Event Loop。面試

console.log('同步代碼1');
setTimeout(() => {
    console.log('setTimeout')
}, 0)
new Promise((resolve) => {
  console.log('同步代碼2')
  resolve()
}).then(() => {
    console.log('promise.then')
})
console.log('同步代碼3');
// 最終輸出"同步代碼1"、"同步代碼2"、"同步代碼3"、"promise.then"、"setTimeout"
複製代碼

瞭解了瀏覽器的事件循環機制以後,咱們回頭來看 Vue nextTick。 nextTick 是下次DOM更新循環結束後執行延遲迴調。segmentfault

第一個 tick(圖例中第一個步驟,即'本次更新循環')

  • 首先修改數據,這是同步任務。同一事件循環的全部的同步任務都在主線程上執行,造成一個執行棧,此時還未涉及 DOM 。
  • Vue 開啓一個異步隊列,並緩衝在此事件循環中發生的全部數據改變。若是同一個 watcher 被屢次觸發,只會被推入到隊列中一次。
  • 同步任務在主線程執行,這是第一個task。

第二個 tick(圖例中第二個步驟,即'下次更新循環')

  • 同步任務執行完畢,開始執行異步 watcher 隊列的任務,更新 DOM 。Vue 在內部嘗試對異步隊列使用原生的 Promise.then 和 MO,若是執行環境不支持,會採用setImmediate或者是setTimeout(fn, 0) 代替(不一樣Vue 版本 API 不同)。
  • DOM 更新是第二個 task 。

第三個 tick(圖例中第三個步驟)

  • 當DOM 更新循環結束以後,此時調用下一個task執行,也就是nextTick中註冊的延遲迴調 。$nextTick 其實和第二個 task 是同樣的操做,可是屬於不一樣的 task。
  • 也就是第三個task。

nextTick 原理

nextTick 的原理,用一句話總結就是『利用 Event loop 事件線程去異步操做』。本質上就是註冊異步任務來對任務進行處理。不一樣的是,在Vue 的不一樣版本對這個異步任務的優雅降級不太同樣。api

一個例子

<div id="example">
   <span>{{test}}</span>
   <button @click="handleClick">change</button>
</div>
複製代碼
var vm = new Vue({
  el: '#example',
  data: { 
    test: 'begin',
  },
  methods: {
    handleClick: function() {
      this.test = 1;
      console.log('script')
      this.$nextTick(function () { 
        console.log('nextTick')
      });
      Promise.resolve().then(function () {
        console.log('promise')
      })
    }
  }
});
複製代碼

Vue 2.4 輸出 script、nextTick、promise。nextTick 執行順序的。測試源碼:連接數組

Vue 2.5+ 中,這段代碼的輸出順序是 script、promise、nextTick。測試源碼:連接promise

Vue 2.6+ 中,輸出 script、nextTick、promise。雖然這裏和 Vue2.4 輸出一致,可是內部實現不太同樣,後面會講到。

注意:這裏輸出的順序並非惟一的,還和 API 兼容性有關係。

看看源碼,原來很簡單

nextTick 的源碼很簡單(示例截圖:2.6.11,2.x 版本這裏差異不大,差異在於 timerFunc 的包裝,後面會講到),就只有幾行代碼。大體步驟以下: image.png

經過數組 callbacks 來存儲用戶註冊的回調。聲明瞭變量 pending 來標記是否正在執行任務。這裏使用一個異步鎖,等待任務隊列執行完畢以後,在執行下一個任務。當前任務隊列正常進行時,將 pending 設置爲 true,每當任務被執行完成時將 pending 設置爲 false,這樣就能夠經過 pending 的值來判斷當前的任務隊列是否在執行,新來的任務是否須要放到下一次的任務隊列中。在當前的隊列中,執行函數 flushCallbacks。當這個函數被觸發時,會將 callbacks 中的全部函數依次執行,而後清空 callbacks,並將 pending 設置爲 false。即一輪事件循環中,flushCallbacks 只會執行一次。這裏須要注意,執行 flushCallbacks 函數時備份回調函數隊列。由於,會出現這麼一種狀況 nextTick 的回調函數中還使用 nextTick。若是 flushCallbacks 不作特殊處理,直接循環執行回調函數,會致使裏面nextTick 中的回調函數會直接進入回調隊列。

nextTick timerFunc 進化史

2.4 版本以前

從 Vue 的 git 上拉取了2.0版本以後關於 timerFunc 的包裝,發如今 2.4 版本以前包裝都是同樣的。優雅降級方案爲:

image.png

可是這樣的方案,在後續的版本中已經代表是有必定的問題,問題在於因爲 microTask 的執行優先級很是高,在某些場景之下它甚至要比事件冒泡還要快,就會致使一些詭異的問題。 例如:

issues:link

2.5 版本

針對 2.5 版本以前的問題,進行了一個 timerFunc 的從新包裝:

image.png

在 2.5 版本中,將 microTask 混合 macroTask 進行優雅降級,可是這個方案也存在一些問題:

issues:link

因爲截圖可能問題不太明顯,codepen.io/ericcirone/…,你們有興趣能夠看源測試代碼,問題點在於當頁面在1000px(大於1000,小於1000)來回切換時,列表顯示影藏存在 1s 的閃爍。本質上緣由是優先使用 macroTask,對一些有重繪和動畫的場景會有性能的影響,形成了閃爍。

2.6 版本

爲了解決以前版本的一些歷史問題,最終 nextTick 採起的策略是默認走 microTask ,對於一些 DOM 的交互事件,如 v-on 綁定的事件回調處理函數的處理,會強制走 macroTask。在源碼層面上也存在一個優雅的降級,以下:

image.png

總結

nextTick 在面試中會常常出現,面試官通常經過 nextTick 考驗候選人的 Event Loop,或者經過 Event Loop 衍生 nextTick。文章從幾個方面淺析了 Vue 的 nextTick 的原理,也從Vue 升級版原本窺視了 nextTick 的前世此生,但願對正在閱讀的你有所幫助。

以上就是本文的所有內容。謝謝觀看,若是你還以爲不錯,幫忙點個贊,謝謝。

參考

相關文章
相關標籤/搜索