介绍一下MutationObserverAPI

105 阅读3分钟
  1. 作用

MutationObserver 接口提供了监视对 DOM 树所有变化(包含节点、属性、文本内容的变动)的能力。

  1. 微任务

和EventTargetAPI的addEventListener相比:

共同点: 两者的回调函数执行都是异步任务,都要在主执行栈之后执行;

不同点

  1. addEventListener的回调函数会进入宏任务队列;MutationObserver的回调函数会进入微任务队列;

  2. addEventListener的触发方式是同步触发;比如,点击后,回调函数立即进入宏任务队列。MutationObserver的监听是异步触发,在所有的DOM操作完成后才触发使回调函数进入微任务队列。

比如当前有几个对DOM的操作,MutationObserver的回调函数必须要等到最后一个操作结束才会进入微任务队列。

微任务在dom渲染之前执行,宏任务在dom渲染之后执行。

  1. 使用

var MutationObserver: new (callback: MutationCallback) => MutationObserver

interface MutationObserver {
    // 阻止 MutationObserver 实例继续接收的通知,直到再次调用其 observe() 方法,该观察者对象包含的回调函数都不会再被调用
    disconnect(): void;
    // 配置 MutationObserver 在 DOM 更改匹配给定选项时,通过其回调函数开始接收通知。
    observe(target: Node, options?: MutationObserverInit): void// 从 MutationObserver 的通知队列中删除所有待处理的通知,并将它们返回到 MutationRecord 对象的新 Array 中。
    takeRecords(): MutationRecord[];
}

interface MutationObserverInit {
    // 一个用于声明哪些属性名会被监听的数组。如果不声明该属性,所有属性的变化都将触发通知。
    attributeFilter?: string[];
   // 当为 true 时,记录上一次被监听的节点的属性变化;可查阅监听属性值了解关于观察属性变化和属性值记录的详情。默认值为 false。
    attributeOldValue?: boolean;
    // 当为 true 时观察所有监听的节点属性值的变化。默认值为 true,当声明了 attributeFilter 或 attributeOldValue,默认值则为 false。
    attributes?: boolean;
    // 当为 true 时,监听声明的 target 节点上所有字符的变化。默认值为 true,如果声明了 characterDataOldValue,默认值则为 false
    characterData?: boolean;
    // 当为 true 时,记录前一个被监听的节点中发生的文本变化。默认值为 false
    characterDataOldValue?: boolean;
    // 当为 true 时,监听 target 节点中发生的节点的新增与删除(同时,如果 subtree 为 true,会针对整个子树生效)。默认值为 false。
    childList?: boolean;
    // 当为 true 时,将会监听以 target 为根节点的整个子树。包括子树中所有节点的属性,而不仅仅是针对 target。默认值为 false
    subtree?: boolean;
}

举一个例子

  function ExampleComponent(props){
      ...
      
      const domId = useRef('');
      const treeContentDivRef = useRef(null);
      
      /**
       * 这里用MutationObserver是因为select选中的节点可能处于某个折叠的父节点中,
       * 那么此时需要等到父节点,才会生成该节点,
       * 如果不通过MutationObserver,则会导致该情况无法顺利滚动到该节点
       */
      const observer = new MutationObserver(() => {
        if (quickSearchSelectKeyRef.current) {
          const anchor = document.getElementById(domId.current);
          if (anchor) {
            // MutationObserver的回调函数是属于微任务,
            // 微任务在dom渲染之前执行,所以会导致滚动触发不那么正确
            setTimeout(() => {
              anchor.scrollIntoView({
                behavior: 'smooth',
              });
              quickSearchSelectKeyRef.current = '';
            });
          }
        }
      });
      
      useEffect(() => {
        if (treeContentDivRef.current) {
          observer.observe(treeContentDivRef.current, { attributes: true, childList: true, subtree: true });
          return () => {
            observer.disconnect();
          };
        }
      }, []);
  
      ...
      
      return <Tree ref={treeContentDivRef} />
  }

这里列了部分关键代码,该功能是我通过一个Select选中了Tree中的某个节点,如果选中后直接scroll过去,可能会因为该节点的父节点还未渲染出展开状态,导致该节点还没渲染出来,scroll会不成功。所以加了个MutationObserver,监听到Tree发生变化之后,再进行scroll。