MutationObserver(下文称MO),用来检测DOM mutaion(突变)的API,其出现是对Mutation Event(下文称ME)的替代。
    一旦DOM发生变化,ME即会调用事件绑定时的回调,如果DOM变化过快过多,这种同步调用会消耗较大性能,从而影响整体体验。而MO与ME最大的不同就在于,MO会将监听到的DOM变化收集起来,当变化完毕时,再一次性处理。这也是我们理解MO API的一个触点。
    如果要监听下方的DOM,可以有如下几步。

    1. <p contenteditable>
    2. <span>Finch</span>
    3. <span>Reese</span>
    4. <span>Root</span>
    5. <span>Shaw</span>
    6. <span>Lionel</span>
    7. </p>

    一、新建一个MO实例
    新建MO实例需要一个callback参数,处理监听到的所有DOM mutaions。而收集到的mutations会集成一个数组,作为参数,传递给callback。
    每个mutation都是一个MutationRecord对象,包含如下内容:

    1. {
    2. type: "characterData", // 变化的类型,例如当文本内容改变时,值为 charaterData
    3. target: text, // 改变的节点
    4. addedNodes: NodeList [], // 添加的节点
    5. removedNodes: NodeList [], // 删除的节点
    6. previousSibling: null, // 添加或删除节点的上一个兄弟节点
    7. nextSibling: null, // 添加或删除节点的下一个兄弟节点
    8. attributeName: null, // 被改变的属性名
    9. attributeNamespace: null, // 被改变的属性名命名空间
    10. oldValue: null, // 根据type不同而有所改变
    11. __proto__: MutationRecord
    12. }

    更详细的MutationRecord内容可参考MDN的MutaionRecord页面张鑫旭老师一篇DOM监测的博文

    1. function mutationCall (mutationArr) {
    2. // 可以根据变化的类型不同对不同的DOM改变做出处理
    3. mutationArr.forEach(m => {
    4. if (m.type === 'attributes') {
    5. console.log(m)
    6. } else if (m.type === 'characterData') {
    7. console.log(m)
    8. } else {
    9. // ...
    10. })
    11. }
    12. let observer = new MutationObserver(mutationCall)

    此时我们新建了一个带有callback的MO实例,但要真正开始监测DOM变化,还需要下面一步。

    二、调用observe方法

    MO实例有三个方法,observe开始监听,在MO实例与DOM之间搭建一个电话线路,使得MO可以监听到来自该DOM的变化信息。disconnect则是掐断这一线路。实际是一个注册与取消注册的关系。第三个takeRecords先不说。

    observe需要两个参数,要监测的target和描述要监测什么样的变化的config。target是一个Node,config是一个MutationObserverInit对象:

    1. {
    2. attributes: false, // true以监测target节点的属性变动
    3. attributeFilter: [], // 要监测哪些属性的变化,空数组表示所有。必须设置attributes为true。
    4. characterData: false, // true以监测节点字符数据的变化
    5. subtree: false, // true以监测子节点的变化
    6. childList: false, // true以监测target子节点的添加和删除
    7. // ...
    8. }

    其中attributes/childList/characterData必须有一个为true,且subtree不可单独为true,否则监听节点的具体什么变动无从得知。更多详细内容可以参考MDN的MutationObserverInit页面

    1. let target = document.querySelector('p')
    2. let config = {
    3. characterData: true,
    4. attributes: true,
    5. subtree: true
    6. }
    7. observer.observe(target, config) // 至此开始监听 p 的DOM变化

    如果有切断监听的业务需求,MO提供了disconnect方法,阻止MO实例的监听,直到再次调用observe方法。
    takeRecords常在disconnect方法调用前调用,以收集还未处理的DOM变动,即所有已经检测到但还未向观察者报告的变动。

    实际上,Vue2的$nextTick API就是用MO实现的,因为MO在整个EventLoop过程里归属于微任务MicroTask,这是我从Vue.js异步更新DOM策略及nextTick这篇文章得来,也正是这篇文章使我了解到MO API,强烈推荐阅读。

    PS:以上涉及的例子放在MO Demo