「源碼剖析」NextTick到底有什么作用
在vue中每次監(jiān)聽(tīng)到數(shù)據(jù)變化的時(shí)候,都會(huì)去調(diào)用notify通知依賴更新,觸發(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 < queue.length; 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.length; 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方法中的去依賴更新,在更新的過(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í)行。