為什么默認優(yōu)先使用 micro task
呢,是利用其高優(yōu)先級的特性,保證隊列中的微任務(wù)在一次循環(huán)全部執(zhí)行完畢。
強制 macro task
的方法是在綁定 DOM 事件的時候,默認會給回調(diào)的 handler 函數(shù)調(diào)用 withMacroTask
方法做一層包裝 handler = withMacroTask(handler)
,它保證整個回調(diào)函數(shù)執(zhí)行過程中,遇到數(shù)據(jù)狀態(tài)的改變,這些改變都會被推到 macro task
中。以上實現(xiàn)在 src/platforms/web/runtime/modules/events.js 的 add
方法中,可以自己看一看具體代碼。
剛好在寫這篇文章的時候思否上有人問了個問題 vue 2.4 和2.5 版本的@input事件不一樣 ,這個問題的原因也是因為2.5之前版本的DOM事件采用 micro task
,而之后采用 macro task
,解決的途徑參考 < Vue.js 升級踩坑小記> 中介紹的幾個辦法,這里就提供一個在mounted鉤子中用 addEventListener
添加原生事件的方法來實現(xiàn),參見 CodePen。
說這么多,不如來個例子,執(zhí)行參見 CodePen
<p id="app"> <span id='name' ref='name'>{{ name }}</span> <button @click='change'>change name</button> <p id='content'></p> </p> <script> new Vue({ el: '#app', data() { return { name: 'SHERlocked93' } }, methods: { change() { const $name = this.$refs.name this.$nextTick(() => console.log('setter前:' + $name.innerHTML)) this.name = ' name改嘍 ' console.log('同步方式:' + this.$refs.name.innerHTML) setTimeout(() => this.console("setTimeout方式:" + this.$refs.name.innerHTML)) this.$nextTick(() => console.log('setter后:' + $name.innerHTML)) this.$nextTick().then(() => console.log('Promise方式:' + $name.innerHTML)) } } }) </script>
執(zhí)行以下看看結(jié)果:
同步方式:SHERlocked93 setter前:SHERlocked93 setter后:name改嘍 Promise方式:name改嘍 setTimeout方式:name改嘍
為什么是這樣的結(jié)果呢,解釋一下:
同步方式: 當(dāng)把data中的name修改之后,此時會觸發(fā)name的 setter
中的 dep.notify
通知依賴本data的render watcher去 update
,update
會把 flushSchedulerQueue
函數(shù)傳遞給 nextTick
,render watcher在 flushSchedulerQueue
函數(shù)運行時 watcher.run
再走 diff -> patch
那一套重渲染 re-render
視圖,這個過程中會重新依賴收集,這個過程是異步的;所以當(dāng)我們直接修改了name之后打印,這時異步的改動還沒有被 patch
到視圖上,所以獲取視圖上的DOM元素還是原來的內(nèi)容。
setter前: setter前為什么還打印原來的是原來內(nèi)容呢,是因為 nextTick
在被調(diào)用的時候把回調(diào)挨個push進callbacks數(shù)組,之后執(zhí)行的時候也是 for
循環(huán)出來挨個執(zhí)行,所以是類似于隊列這樣一個概念,先入先出;在修改name之后,觸發(fā)把render watcher填入 schedulerQueue
隊列并把他的執(zhí)行函數(shù) flushSchedulerQueue
傳遞給 nextTick
,此時callbacks隊列中已經(jīng)有了 setter前函數(shù)
了,因為這個 cb
是在 setter前函數(shù)
之后被push進callbacks隊列的,那么先入先出的執(zhí)行callbacks中回調(diào)的時候先執(zhí)行 setter前函數(shù)
,這時并未執(zhí)行render watcher的 watcher.run
,所以打印DOM元素仍然是原來的內(nèi)容。
setter后: setter后這時已經(jīng)執(zhí)行完 flushSchedulerQueue
,這時render watcher已經(jīng)把改動 patch
到視圖上,所以此時獲取DOM是改過之后的內(nèi)容。
Promise方式: 相當(dāng)于 Promise.then
的方式執(zhí)行這個函數(shù),此時DOM已經(jīng)更改。
setTimeout方式: 最后執(zhí)行macro task的任務(wù),此時DOM已經(jīng)更改。
注意,在執(zhí)行 setter前函數(shù)
這個異步任務(wù)之前,同步的代碼已經(jīng)執(zhí)行完畢,異步的任務(wù)都還未執(zhí)行,所有的 $nextTick
函數(shù)也執(zhí)行完畢,所有回調(diào)都被push進了callbacks隊列中等待執(zhí)行,所以在setter前函數(shù)
執(zhí)行的時候,此時callbacks隊列是這樣的:[setter前函數(shù)
,flushSchedulerQueue
,setter后函數(shù)
,Promise方式函數(shù)
],它是一個micro task隊列,執(zhí)行完畢之后執(zhí)行macro task setTimeout
,所以打印出上面的結(jié)果。
另外,如果瀏覽器的宏任務(wù)隊列里面有setImmediate
、MessageChannel
、setTimeout/setInterval
各種類型的任務(wù),那么會按照上面的順序挨個按照添加進event loop中的順序執(zhí)行,所以如果瀏覽器支持MessageChannel
, nextTick
執(zhí)行的是 macroTimerFunc
,那么如果 macrotask queue 中同時有 nextTick
添加的任務(wù)和用戶自己添加的 setTimeout
類型的任務(wù),會優(yōu)先執(zhí)行 nextTick
中的任務(wù),因為MessageChannel
的優(yōu)先級比 setTimeout
的高,setImmediate
同理。
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com