在vue中每次監聽到數據變化的時候,都會去調用notify通知依賴更新,觸發watcher中的update方法。
- update () {
- /* istanbul ignore else */
- if (this.lazy) {
- } else if (this.sync) {
- } else {
- this.get()
- //queueWatcher(this)
- }
- }
如果通過watcher中的get方法去重新渲染組件,那么在渲染的過程中假如多次更新數據會導致同一個watcher被觸發多次,這樣會導致重復的數據計算和DOM的操作。如下圖所示,修改3次message之后DOM被操作了3次。

為了解決上述問題,不去直接調用get方法而是將每次調用update方法后需要批處理的wather暫存到一個隊列當中,如果同一個 watcher 被多次觸發,通過wacther 的id屬性對其去重,只會被推入到隊列中一次。然后,等待所有的同步代碼執行完畢之后在下一個的事件循環中,Vue 刷新隊列并執行實際 (已去重的) 工作。
- let has: { [key: number]: ?true } = {}
- let waiting = false
- export function queueWatcher (watcher: Watcher) {
- const id = watcher.id //對watcher去重
- if (has[id] == null) {
- has[id] = true
- queue.push(watcher);
- if (!waiting) { //節流
- waiting = true
- nextTick(flushSchedulerQueue)
- }
- }
調用watcher的run方法異步更新DOM
- let has: { [key: number]: ?true } = {}
- function flushSchedulerQueue () {
- let watcher, id
- queue.sort((a, b) => a.id - b.id)
- for (index = 0; index < queue.length; index++) {
- watcher = queue[index]
- if (watcher.before) {
- watcher.before()
- }
- id = watcher.id
- has[id] = null //清空id
- watcher.run() //更新值
- }
- resetSchedulerState() //清空watcher隊列
- }
- function resetSchedulerState () {
- index = queue.length = 0
- has = {}
- waiting = false
- }
在vue內部調用nextTick(flushSchedulerQueue),vm.$nextTick方法調用的也是nextTick()方法
- Vue.prototype.$nextTick = function (cb) {
- nextTick(cb,this);
- };
那么多次調用nextTick方法是怎么處理的呢?
- const callbacks = []
- let pending = false
- export function nextTick (cb?: Function, ctx?: Object) {
- callbacks.push(() => {
- if (cb) {
- try {
- cb.call(ctx)
- } catch (e) {
- handleError(e, ctx, 'nextTick')
- }
- }
- })
- if (!pending) {
- pending = true
- timerFunc()
- }
- }
nextTick將所有的回調函數暫存到了一個隊列中,然后通過異步調用更新去依次執行隊列中的回調函數。
- function flushCallbacks () {
- pending = false
- const copies = callbacks.slice(0)
- callbacks.length = 0
- for (let i = 0; i < copies.length; i++) {
- copies[i]()
- }
- }
nextTick函數中異步更新對兼容性做了處理,使用原生的 Promise.then、MutationObserver 和 setImmediate,如果執行環境不支持,則會采用 setTimeout(fn, 0) 代替。
Promise
- if (typeof Promise !== 'undefined' && isNative(Promise)) {
- const p = Promise.resolve()
- timerFunc = () => {
- p.then(flushCallbacks)
- }
- }
MutationObserver
MutationObserver 它會在指定的DOM發生變化時被調用。創建了一個文本DOM,通過監聽字符值的變化,當文本字符發生變化的時候調用回調函數。
- if (!isIE && typeof MutationObserver !== 'undefined' && (
- isNative(MutationObserver) ||
- MutationObserver.toString() === '[object MutationObserverConstructor]'
- )) {
- let counter = 1
- const observer = new MutationObserver(flushCallbacks)
- const textNode = document.createTextNode(String(counter))
- observer.observe(textNode, {
- characterData: true
- })
- timerFunc = () => {
- counter = (counter + 1) % 2
- textNode.data = String(counter)
- }
- }
setImmediate
setImmediate該方法用作把一些需要持續運行的操作放在一個其他函數里,在瀏覽器完成后面的其他語句后,就立即執行此替換函數。
- if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
- timerFunc = () => {
- setImmediate(flushCallbacks)
- }
- }else{
- timerFunc = () => {
- setTimeout(flushCallbacks, 0)
- }
- }
總結
vue渲染DOM的時候觸發set方法中的去依賴更新,在更新的過程中watcher不是每次都去執行去觸發DOM的更新,而是通過對wather的去重之后,通過nextTick異步調用觸發DOM更新。
nextTick()就是一個異步函數,在異步函數中通過隊列批處理nextTick傳入的回調函數cb,但是隊列彼此不是同時進行的,通過節流的方式依次執行。
原文地址:https://mp.weixin.qq.com/s/UGk4-lOk0nLyCg5Pno1HQA