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

明白为什么settimeout为0,可以做到同步的setter全部执行完毕以后,在下一次tick中批量执行去重后的watch。问题是mutationObserver如何做到nextTick这点?这不是用来监听节点变化的么? mutationObserver如果可以做到nexttick的效果,那么和settimeout的的区别在哪里。为什么优先使用MutationObserver。
关注者
178
被浏览
9460

6 个回答

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)
    }
为什么?