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

為了解決上述問題,不去直接調(diào)用get方法而是將每次調(diào)用update方法后需要批處理的wather暫存到一個隊列當(dāng)中,如果同一個 watcher 被多次觸發(fā),通過wacther 的id屬性對其去重,只會被推入到隊列中一次。然后,等待所有的同步代碼執(zhí)行完畢之后在下一個的事件循環(huán)中,Vue 刷新隊列并執(zhí)行實際 (已去重的) 工作。
- 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) { //節(jié)流
- waiting = true
- nextTick(flushSchedulerQueue)
- }
- }
調(diào)用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內(nèi)部調(diào)用nextTick(flushSchedulerQueue),vm.$nextTick方法調(diào)用的也是nextTick()方法
- Vue.prototype.$nextTick = function (cb) {
- nextTick(cb,this);
- };
那么多次調(diào)用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將所有的回調(diào)函數(shù)暫存到了一個隊列中,然后通過異步調(diào)用更新去依次執(zhí)行隊列中的回調(diào)函數(shù)。
- function flushCallbacks () {
- pending = false
- const copies = callbacks.slice(0)
- callbacks.length = 0
- for (let i = 0; i < copies.length; i++) {
- copies[i]()
- }
- }
nextTick函數(shù)中異步更新對兼容性做了處理,使用原生的 Promise.then、MutationObserver 和 setImmediate,如果執(zhí)行環(huán)境不支持,則會采用 setTimeout(fn, 0) 代替。
Promise
- if (typeof Promise !== 'undefined' && isNative(Promise)) {
- const p = Promise.resolve()
- timerFunc = () => {
- p.then(flushCallbacks)
- }
- }
MutationObserver
MutationObserver 它會在指定的DOM發(fā)生變化時被調(diào)用。創(chuàng)建了一個文本DOM,通過監(jiān)聽字符值的變化,當(dāng)文本字符發(fā)生變化的時候調(diào)用回調(diào)函數(shù)。
- 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該方法用作把一些需要持續(xù)運行的操作放在一個其他函數(shù)里,在瀏覽器完成后面的其他語句后,就立即執(zhí)行此替換函數(shù)。
- if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
- timerFunc = () => {
- setImmediate(flushCallbacks)
- }
- }else{
- timerFunc = () => {
- setTimeout(flushCallbacks, 0)
- }
- }
總結(jié)
vue渲染DOM的時候觸發(fā)set方法中的去依賴更新,在更新的過程中watcher不是每次都去執(zhí)行去觸發(fā)DOM的更新,而是通過對wather的去重之后,通過nextTick異步調(diào)用觸發(fā)DOM更新。
nextTick()就是一個異步函數(shù),在異步函數(shù)中通過隊列批處理nextTick傳入的回調(diào)函數(shù)cb,但是隊列彼此不是同時進行的,通過節(jié)流的方式依次執(zhí)行。

































