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

[图片] [图片] 明白为什么settimeout为0,可以做到同步的setter全部执行完毕以后,在下一次tick中批量执行去重后的watch。问题…
关注者
292
被浏览
65,562

8 个回答

更新一下,现在 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)
    }
  • 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