「源碼剖析」nextTick到底有什么作用
點(diǎn)擊上方“前端簡(jiǎn)報(bào)”,選擇“設(shè)為星標(biāo)”
第一時(shí)間關(guān)注技術(shù)干貨!

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

為了解決上述問(wèn)題,不去直接調(diào)用get方法而是將每次調(diào)用update方法后需要批處理的wather暫存到一個(gè)隊(duì)列當(dāng)中,如果同一個(gè) watcher 被多次觸發(fā),通過(guò)wacther 的id屬性對(duì)其去重,只會(huì)被推入到隊(duì)列中一次。然后,等待所有的同步代碼執(zhí)行完畢之后在下一個(gè)的事件循環(huán)中,Vue 刷新隊(duì)列并執(zhí)行實(shí)際 (已去重的) 工作。
let?has:?{?[key:?number]:??true?}?=?{}
let?waiting?=?false
export?function?queueWatcher?(watcher:?Watcher)?{
??const?id?=?watcher.id //對(duì)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?????watcher?=?queue[index]
????if?(watcher.before)?{
??????watcher.before()
????}
????id?=?watcher.id
????has[id]?=?null //清空id
????watcher.run() //更新值
??}
??
??resetSchedulerState() //清空watcher隊(duì)列
}
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ù)暫存到了一個(gè)隊(duì)列中,然后通過(guò)異步調(diào)用更新去依次執(zhí)行隊(duì)列中的回調(diào)函數(shù)。
function?flushCallbacks?()?{
??pending?=?false
??const?copies?=?callbacks.slice(0)
??callbacks.length?=?0
??for?(let?i?=?0;?i?????copies[i]()
??}
}
nextTick函數(shù)中異步更新對(duì)兼容性做了處理,使用原生的?Promise.then、MutationObserver?和?setImmediate,如果執(zhí)行環(huán)境不支持,則會(huì)采用?setTimeout(fn, 0)?代替。
Promise
if?(typeof?Promise?!==?'undefined'?&&?isNative(Promise))?{
??const?p?=?Promise.resolve()
??timerFunc?=?()?=>?{
????p.then(flushCallbacks)
??}
}

? ?
MutationObserver
MutationObserver 它會(huì)在指定的DOM發(fā)生變化時(shí)被調(diào)用。創(chuàng)建了一個(gè)文本DOM,通過(guò)監(jiān)聽(tīng)字符值的變化,當(dāng)文本字符發(fā)生變化的時(shí)候調(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ù)運(yùn)行的操作放在一個(gè)其他函數(shù)里,在瀏覽器完成后面的其他語(yǔ)句后,就立即執(zhí)行此替換函數(shù)。
if?(typeof?setImmediate?!==?'undefined'?&&?isNative(setImmediate))?{
??timerFunc?=?()?=>?{
????setImmediate(flushCallbacks)
??}
}else{
??timerFunc?=?()?=>?{
????setTimeout(flushCallbacks,?0)
??}
}

總結(jié)




vue渲染DOM的時(shí)候觸發(fā)set方法中的去依賴(lài)更新,在更新的過(guò)程中watcher不是每次都去執(zhí)行去觸發(fā)DOM的更新,而是通過(guò)對(duì)wather的去重之后,通過(guò)nextTick異步調(diào)用觸發(fā)DOM更新。
nextTick()就是一個(gè)異步函數(shù),在異步函數(shù)中通過(guò)隊(duì)列批處理nextTick傳入的回調(diào)函數(shù)cb,但是隊(duì)列彼此不是同時(shí)進(jìn)行的,通過(guò)節(jié)流的方式依次執(zhí)行。
精選推薦

前端簡(jiǎn)報(bào)
02?Feb?2020


