??在vue的代碼中,有時(shí)候會(huì)用到this.$nextTick,這個(gè)方法的回調(diào)函數(shù)里可以獲取到數(shù)據(jù)更新之后的DOM,使用的方法這里就不說(shuō)了。在vue中可以簡(jiǎn)單理解為nextTick是下一次dom更新。
??另外,在我之前寫(xiě)的源碼中,關(guān)于Watcher中的update方法只寫(xiě)了簡(jiǎn)單的同步更新,在vue的源碼中,update方法,除非watcher中指定了sync為true,否則都為異步更新。異步更新會(huì)通過(guò)queueWatcher把當(dāng)前觀察者加入一個(gè)全局的watcher隊(duì)列中,該隊(duì)列中的watcher都會(huì)在nextTick后統(tǒng)一調(diào)用。
事件循環(huán)(關(guān)于macroTask與microTask)
??簡(jiǎn)單來(lái)說(shuō),事件循環(huán)是這樣的,先執(zhí)行一個(gè)macroTask,然后執(zhí)行完microTask隊(duì)列中所有的microTask,然后再去執(zhí)行一個(gè)macroTask。
??macroTask有這幾種:setTimeout,setInterval,setImmediate,postMessage,MessageChannel,I/O,UI渲染等。
??microTask有這幾種:原生Promise,MutationObserver,process.nextTick(Node環(huán)境)
看了下源碼,在vue2.5.0之前,vue對(duì)nextTick的處理都是這樣的:
1.是否有原生的Promise對(duì)象,如果是,就使用Promise.then異步觸發(fā)nextTick
2.如果不支持Promise,就使用MutationObserver來(lái)異步觸發(fā)nextTick
3.如果都不支持,則使用setTimeout。
??總而言之,從大部分的瀏覽器來(lái)說(shuō),所有的nextTick都是一個(gè)microTask。
??但是,如果去看最新的vue源碼,就會(huì)發(fā)現(xiàn)獨(dú)立了一個(gè)next-tick.js文件出來(lái),里面的邏輯有了一些改變。
至于改動(dòng)的原因,源碼中也有說(shuō)明,我試著翻譯了一下:
// Here we have async deferring wrappers using both microtasks and (macro) tasks.
// In < 2.4 we used microtasks everywhere, but there are some scenarios where
// microtasks have too high a priority and fire in between supposedly
// sequential events (e.g. #4521, #6690) or even between bubbling of the same
// event (#6566). However, using (macro) tasks everywhere also has subtle problems
// when state is changed right before repaint (e.g. #6813, out-in transitions).
// Here we use microtask by default, but expose a way to force (macro) task when
// needed (e.g. in event handlers attached by v-on).
//
// 這里我們的異步函數(shù)一起使用了microtasks和macrotasks,
// 在2.4版本之前,我們?cè)谌魏蔚胤蕉际怯昧薽icrotasks,但是這里是一些microtasks有錯(cuò)誤的場(chǎng)景
// 由于microtasks有著太高的優(yōu)先級(jí),它在兩次本應(yīng)該順序執(zhí)行的事件之間觸發(fā)了(比如 #4521, #6690),
// 甚至在同一個(gè)事件冒泡的過(guò)程之間觸發(fā)了(比如#6566)。
// 然而,所有的地方都使用macroTask也會(huì)有一些小問(wèn)題,比如當(dāng)狀態(tài)剛好在瀏覽器重繪之前改變(#6813)
// 因此,我們默認(rèn)使用了microtask,但是提供了一個(gè)方法強(qiáng)制使用macroTask如果需要的話。(比如在事件的綁定中)
關(guān)于事件冒泡的情況,我寫(xiě)了如下一個(gè)小demo:
<div id="a">
aaaaa
<div id="b">bbbbb</div>
</div>
<script>
var $a = document.querySelector('#a')
var $b = document.querySelector('#b')
$b.addEventListener('click', function () {
Promise.resolve()
.then(function () {
console.log('microTask')
})
console.log('b')
})
$a.addEventListener('click', function () {
console.log('a')
})
</script>
??按理說(shuō)nextTick應(yīng)該在點(diǎn)擊完b然后更新完DOM之后執(zhí)行,但是如果使用了microTask,就像上面的代碼,輸出的結(jié)果為:b,microTask,a,也就是說(shuō)冒泡到a上面的事件里如果有狀態(tài)的改變,那么在nextTick中便無(wú)法獲得,其余的場(chǎng)景原理應(yīng)該也是差不多。如果把這里的microTask換成一個(gè)macroTask,那么在nextTick中就能獲得這兩個(gè)事件之后的DOM。另外,據(jù)我推測(cè),涉及到事件的,不管是冒泡還是捕獲的,應(yīng)該都是一個(gè)macroTask。
??說(shuō)回源碼,為了解決這個(gè)問(wèn)題,vue2.5之后,next-tick.js中有這樣一段代碼:
export function withMacroTask (fn: Function): Function {
return fn._withTask || (fn._withTask = function () {
useMacroTask = true
const res = fn.apply(null, arguments)
useMacroTask = false
return res
})
}
?這個(gè)方法的作用是,回調(diào)函數(shù)(fn)中的狀態(tài)改變引起的nextTick操作,異步任務(wù)使用macroTask方法,具體的實(shí)現(xiàn)可以參照源碼:源碼。全局去搜索withMacroTask,可以發(fā)現(xiàn)目前只有events模塊使用了這個(gè)方法。
?另外,最新的源碼中,使用的macroTask的優(yōu)先級(jí)為:setImmediate(只有IE支持),MessageChannel,setTimeout