Vue 中如何使用 MutationObserver 做批量處理?

明白為什麼settimeout為0,可以做到同步的setter全部執行完畢以後,在下一次tick中批量執行去重後的watch。問題是mutationObserver如何做到nextTick這點?這不是用來監聽節點變化的么?

mutationObserver如果可以做到nexttick的效果,那麼和settimeout的的區別在哪裡。為什麼優先使用MutationObserver。


更新一下,現在 Vue 的 nextTick 實現移除了 MutationObserver 的方式(兼容性原因),取而代之的是使用 MessageChannel。


Jake Archibald 有一篇介紹 task 和 microtask 的文章,可以了解一下:Tasks, microtasks, queues and schedules

JS 的 event loop 執行時會區分 task 和 microtask,引擎在每個 task 執行完畢,從隊列中取下一個 task 來執行之前,會先執行完所有 microtask 隊列中的 microtask。

setTimeout 回調會被分配到一個新的 task 中執行,而 Promise 的 resolver、MutationObserver 的回調都會被安排到一個新的 microtask 中執行,會比 setTimeout 產生的 task 先執行。要創建一個新的 microtask,優先使用 Promise,如果瀏覽器不支持,再嘗試 MutationObserver。實在不行,只能用 setTimeout 創建 task 了。為啥要用 microtask?根據HTML Standard,在每個 task 運行完以後,UI 都會重渲染,那麼在 microtask 中就完成數據更新,當前 task 結束就可以得到最新的 UI 了。反之如果新建一個 task 來做數據更新,那麼渲染就會進行兩次。(當然,瀏覽器實現有不少不一致的地方,上面 Jake 那篇文章里已經有提到。)

至於 MutationObserver 如何模擬 nextTick 這點,直接看源碼,其實就是創建一個 TextNode 並監聽內容變化,然後要 nextTick 的時候去改一下這個節點的文本內容:

var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () =&> {
counter = (counter + 1) % 2
textNode.data = String(counter)
}


實際上不是用 "使用MutationObserver做批量處理", 而是利用 MutationObserver 來做非同步回調. 在 js 里, 如果你不用非同步 API 你就沒法實現非同步(廢話), 那麼問題就在於, 具體用什麼非同步 API 來實現非同步呢?

Vue 關於 nextTick() , 用到了很多種非同步 API, 就是因為 setTimeout() 的間隔太大. 原本 setTimeout/setInterval 的 timer 不小於 4ms 這個行為是 vendor specific 的, 但後來被 HTML5 標準化了. 而 vue 用這麼多奇技淫巧, 還是想減少這個非同步的間隔.

按照 mozilla 社區的推薦, 用 window.postMessage() 就可以了, 但根據 @尤雨溪 的說法, postMessage() 在 macrotask queue 里, 比 MutationObserver 的 microtask queue 晚.

setTimeout with a shorter delay

New timerFunc in rc7 is laggy · Issue #3771 · vuejs/vue


https://www.zhihu.com/question/36972010?utm_source=wechat_sessionutm_medium=socialfrom=singlemessage


Vue這裡用原生Promise也好,還是用MO也好,其實都是為了模擬Node中的nextTick,讓回==調非同步且儘早調用==,用setTimeout最快也是4ms發以後調用回調,而使用另外兩個,則可以讓回調在本次Event Loop之後立馬調用(從調用時機來看,基本已經相當於非同步了),按照Node中的時間大致換算一下,大概是千萬分之一秒,也就是萬分之一毫秒後調用,這就比setTimeout的4ms要"快"(其實是早)的多了,題主有興趣可以在控制台試下下面兩段代碼的輸出,時間應該是不一樣的

start=Date.now()

setTimeout(_=&>console.log(Date.now()-start))

===

start=Date.now()

Promise.resolve().then(_=&>console.log(Date.now()-start))

手機回答,代碼不能格式化

另外有不少Promise庫中也是用MO實現的這一特性,比如Q


const alertFn = c =&> () =&> alert(c);
setTimeout(alertFn(1), 0);
setImmediate(alertFn(2));
Promise.resolve().then(alertFn(3));
alertFn(4)();

把上面的代碼在 Console 里運行,自己體會一下,Promise(需引擎原生支持的,Shim 的無效) 的 handler 函數執行的時機與 MutationObserver 調用 callback 的是一樣的,我這裡為了方便就沒寫 MO 的那種了。首先會顯示 4,這一步之前把所有的數據修改完畢,下一個事件循環再對這些變化作處理可以提升效率,避免多次渲染。至於為什麼要用 MO 或 Promise,因為他們屬於 Micro Tasks,比 timer 之類的 Macro Tasks 優先執行。而且 Macro Tasks 被調度的時機一般在 Paint 和 Layout 階段結束後,這樣做也是為了避免多次重渲染。


  • macrotasks: setTimeout ,setInterval, setImmediate,requestAnimationFrame,I/O ,UI渲染
  • microtasks: Promise, process.nextTick, Object.observe, MutationObserver

當一個程序有:setTimeout, setInterval ,setImmediate, I/O, UI渲染,Promise ,process.nextTick, Object.observe, MutationObserver的時候:

1.先執行 macrotasks:I/O -》 UI渲染-》requestAnimationFrame

2.再執行 microtasks :process.nextTick -》 Promise -》MutationObserver -&>Object.observe

3.再把setTimeout setInterval setImmediate【三個貨不討喜】 塞入一個新的macrotasks,依次:setTimeout ,setInterval --》setImmediate

setImmediate(function(){
console.log(1);
},0);
setTimeout(function(){
console.log(2);
},0);
new Promise(function(resolve){
console.log(3);
resolve();
console.log(4);
}).then(function(){
console.log(5);
});
console.log(6);
process.nextTick(function(){
console.log(7);
});
console.log(8);
結果是:3 4 6 8 7 5 2 1


稍微偏個題,剛剛踩過nextTick的坑。nextTick優先使用promise或mutationObserver,是為了加快dom操作,儘可能把多次dom操作集中到一起完成,但這不一定必然是開發者想要的。比如說,我利用循環中的nextTick在移動端渲染一個超長列表,比如說需要10秒,這期間ui和用戶輸入都是不響應的,因為其實都被合併為一次dom操作了,而dom一次性插入這麼多元素就會表現為暫時卡死。改成用setTimeout就沒問題,總時間慢了,但是task之間可以響應輸入了,總的體驗反而大大提高。


目前vue版本更新到了v2.5.2; 最新版中已經不再使用`MuationObserver`了,而改用MessageChannel了, 不過關於微任務,更加詳細的任務調控還是要好好看看Jake大神的文章


推薦閱讀:

有什麼UI組件庫可以兼容三大框架,vue,react,angular嗎?
在react 、 vue 、ng 這些框架火起來之前,是哪些框架比較火?它們現在怎麼樣了?
Vue、React和Angular 2.x,誰是2016年的主流?
vue,react之類的框架是不是弱化了對前端人員js水平的要求?

TAG:前端開發 | 前端工程師 | Vuejs |