快速跳轉:javascript
引用下vue.js官方教程關於vue中異步更新隊列的解釋:html
可能你尚未注意到,Vue 在更新 DOM 時是異步執行的。只要偵聽到數據變化,Vue 將開啓一個隊列,並緩衝在同一事件循環中發生的全部數據變動。若是同一個 watcher 被屢次觸發,只會被推入到隊列中一次。這種在緩衝時去除重複數據對於避免沒必要要的計算和 DOM 操做是很是重要的。而後,在下一個的事件循環「tick」中,Vue 刷新隊列並執行實際 (已去重的) 工做。Vue 在內部對異步隊列嘗試使用原生的 Promise.then、MutationObserver 和 setImmediate,若是執行環境不支持,則會採用 setTimeout(fn, 0) 代替。
爲何要這麼作呢?來看一個簡單的例子:vue
<template> <div> <div>{{test}}</div> </div> </template>
export default { data () { return { test: 0 }; }, mounted () { for(let i = 0; i < 1000; i++) { this.test++; } } }
根據mounted裏的代碼,test一共被觸發了1000次,若是不是異步更新,watcher在被觸發1000次時,每次都會去更新視圖,這是很是很是消耗性能的。所以,vue纔會採用異步更新的操做,若是同一個 watcher 被屢次觸發,只會被推入到隊列中一次。而後在下一次事件循環'tick‘的時候,只需更新一次dom便可。java
咱們來看一下watcher類中的update方法,瞭解下具體這個異步更新代碼是如何實現的。git
update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { /*同步則執行run直接渲染視圖*/ // 基本不會用到sync this.run() } else { /*異步推送到觀察者隊列中,由調度者調用。*/ queueWatcher(this) } } export function queueWatcher (watcher: Watcher) { /*獲取watcher的id*/ const id = watcher.id /*檢驗id是否存在,已經存在則直接跳過,不存在則標記哈希表has,用於下次檢驗*/ if (has[id] == null) { has[id] = true if (!flushing) { /*若是沒有flush掉,直接push到隊列中便可*/ queue.push(watcher) } else { // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately. let i = queue.length - 1 while (i >= 0 && queue[i].id > watcher.id) { i-- } queue.splice(Math.max(i, index) + 1, 0, watcher) } // queue the flush if (!waiting) { waiting = true nextTick(flushSchedulerQueue) } } }
咱們能夠看到,watcher在update時候,調用了一個queueWatcher的方法,將watcher異步推送到觀察者隊列中;調用queueWatcher的時候,首先查看了watcher的id,保證相同的watcher不被屢次推入;waiting是一個標識,若是沒有在waiting, 就異步執行flushSchedulerQueue方法。github
flushSchedulerQueue是下一個tick的時候的回調函數,主要就是去執行watcher中的run方法。看一下源碼:express
/*nextTick的回調函數,在下一個tick時flush掉兩個隊列同時運行watchers*/ function flushSchedulerQueue () { flushing = true let watcher, id // Sort queue before flush. // This ensures that: // 1. Components are updated from parent to child. (because parent is always // created before the child) // 2. A component's user watchers are run before its render watcher (because // user watchers are created before the render watcher) // 3. If a component is destroyed during a parent component's watcher run, // its watchers can be skipped. /* 給queue排序,這樣作能夠保證: 1.組件更新的順序是從父組件到子組件的順序,由於父組件老是比子組件先建立。 2.一個組件的user watchers比render watcher先運行,由於user watchers每每比render watcher更早建立 3.若是一個組件在父組件watcher運行期間被銷燬,它的watcher執行將被跳過。 */ // queue: Array<Watcher> queue.sort((a, b) => a.id - b.id) // do not cache length because more watchers might be pushed // as we run existing watchers for (index = 0; index < queue.length; index++) { watcher = queue[index] id = watcher.id /*將has的標記刪除*/ has[id] = null /*執行watcher*/ watcher.run() // in dev build, check and stop circular updates. if (process.env.NODE_ENV !== 'production' && has[id] != null) { circular[id] = (circular[id] || 0) + 1 if (circular[id] > MAX_UPDATE_COUNT) { warn( 'You may have an infinite update loop ' + ( watcher.user ? `in watcher with expression "${watcher.expression}"` : `in a component render function.` ), watcher.vm ) break } } } // keep copies of post queues before resetting state /**/ /*獲得隊列的拷貝*/ const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() /*重置調度者的狀態*/ resetSchedulerState() // call component updated and activated hooks /*使子組件狀態都改編成active同時調用activated鉤子*/ callActivatedHooks(activatedQueue) /*調用updated鉤子*/ callUpdateHooks(updatedQueue) // devtool hook /* istanbul ignore if */ if (devtools && config.devtools) { devtools.emit('flush') } }
這時候咱們再來看一下vue中文教程裏的代碼:閉包
<div id="example">{{message}}</div>
var vm = new Vue({ el: '#example', data: { message: '123' } }) vm.message = 'new message' // 更改數據 vm.$el.textContent === 'new message' // false Vue.nextTick(function () { vm.$el.textContent === 'new message' // true })
當設置vm.message = 'new message'
時候,該組件並不會當即進行渲染,而是在dep.notify了watcher以後,被異步推到了調度者隊列中,等到nextTick的時候進行更新。nextTick則是會去嘗試原生的Promise.then和MutationObserver,若是都不支持,最差會採用setTimeout(fn, 0)進行異步更新。因此若是想基於更新後的DOM狀態作點什麼,能夠在數據變化以後當即使用Vue.nextTick(callback)對真實DOM進行操做。dom