為什么默認優先使用 micro task
呢,是利用其高優先級的特性,保證隊列中的微任務在一次循環全部執行完畢。
強制 macro task
的方法是在綁定 DOM 事件的時候,默認會給回調的 handler 函數調用 withMacroTask
方法做一層包裝 handler = withMacroTask(handler)
,它保證整個回調函數執行過程中,遇到數據狀態的改變,這些改變都會被推到 macro task
中。以上實現在 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
添加原生事件的方法來實現,參見 CodePen。
說這么多,不如來個例子,執行參見 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>
執行以下看看結果:
同步方式:SHERlocked93 setter前:SHERlocked93 setter后:name改嘍 Promise方式:name改嘍 setTimeout方式:name改嘍
為什么是這樣的結果呢,解釋一下:
同步方式: 當把data中的name修改之后,此時會觸發name的 setter
中的 dep.notify
通知依賴本data的render watcher去 update
,update
會把 flushSchedulerQueue
函數傳遞給 nextTick
,render watcher在 flushSchedulerQueue
函數運行時 watcher.run
再走 diff -> patch
那一套重渲染 re-render
視圖,這個過程中會重新依賴收集,這個過程是異步的;所以當我們直接修改了name之后打印,這時異步的改動還沒有被 patch
到視圖上,所以獲取視圖上的DOM元素還是原來的內容。
setter前: setter前為什么還打印原來的是原來內容呢,是因為 nextTick
在被調用的時候把回調挨個push進callbacks數組,之后執行的時候也是 for
循環出來挨個執行,所以是類似于隊列這樣一個概念,先入先出;在修改name之后,觸發把render watcher填入 schedulerQueue
隊列并把他的執行函數 flushSchedulerQueue
傳遞給 nextTick
,此時callbacks隊列中已經有了 setter前函數
了,因為這個 cb
是在 setter前函數
之后被push進callbacks隊列的,那么先入先出的執行callbacks中回調的時候先執行 setter前函數
,這時并未執行render watcher的 watcher.run
,所以打印DOM元素仍然是原來的內容。
setter后: setter后這時已經執行完 flushSchedulerQueue
,這時render watcher已經把改動 patch
到視圖上,所以此時獲取DOM是改過之后的內容。
Promise方式: 相當于 Promise.then
的方式執行這個函數,此時DOM已經更改。
setTimeout方式: 最后執行macro task的任務,此時DOM已經更改。
注意,在執行 setter前函數
這個異步任務之前,同步的代碼已經執行完畢,異步的任務都還未執行,所有的 $nextTick
函數也執行完畢,所有回調都被push進了callbacks隊列中等待執行,所以在setter前函數
執行的時候,此時callbacks隊列是這樣的:[setter前函數
,flushSchedulerQueue
,setter后函數
,Promise方式函數
],它是一個micro task隊列,執行完畢之后執行macro task setTimeout
,所以打印出上面的結果。
另外,如果瀏覽器的宏任務隊列里面有setImmediate
、MessageChannel
、setTimeout/setInterval
各種類型的任務,那么會按照上面的順序挨個按照添加進event loop中的順序執行,所以如果瀏覽器支持MessageChannel
, nextTick
執行的是 macroTimerFunc
,那么如果 macrotask queue 中同時有 nextTick
添加的任務和用戶自己添加的 setTimeout
類型的任務,會優先執行 nextTick
中的任務,因為MessageChannel
的優先級比 setTimeout
的高,setImmediate
同理。
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com