DOM – MutationObserver
介绍
它和 IntersectionObserver, ResizeObserver 差不多, 都是观察 element 变化的.
它可以观察元素的 attribute 增加, 移除, 修改, append child 等等.
建议先看前 2 篇 IntersectionObserver 和 ResizeObserver 一起了解会比较容易.
new MutationObserver()
const mo = new MutationObserver((mutations) => { console.log("mutations", mutations); }); mo.observe(document.querySelector(".container"), { attributes: true, });
调用方式和 Intersection, ResizeObserver 是一样的.
注意: 它和 IO 和 Resize 有个区别, IO, Resize 调用 observe 第一次会马上触发掉, 但 Mutation 没有, 它会等到真的有改变时才触发.
还有一个特别之处是 observe 的时候需要一个 config. 指定要观察的范围.
出于性能考虑, 观察的范围越广性能越伤, 所以请按需设置哦.
观察 attributes
attributes: true
可以观察到元素添加, 移除, 修改 attribute. (注: 只要有 value set 即便 value 是一样的, 它依然会触发哦, 如果不想这样, 我们可以通过 oldValue 来 filter 掉)
attributeOldValue: true
多了一个 oldValue
attributeFilter: ["contenteditable"]
指定要观察的 attribute, 没有在 list 里面的, 添加, 移除, 修改都不会触发.
观察 Child 和 Descendant Element
childList: true
当元素 appendChild / removeChild 的时候触发.
subtree: true
当元素有子孙后裔插入或移除时触发.
观察 TextNode textcontent
characterData: true & characterDataOldValue: true
const textNode = document.createTextNode("Hello World"); mo.observe(textNode, { characterData: true, characterDataOldValue: true }); setTimeout(() => { textNode.textContent = "SuperMan"; }, 3000);
效果
注意, 一定要是 TextNode 哦.
const p = document.createElement("p"); p.textContent = "Hello World"; mo.observe(p, { characterData: true, characterDataOldValue: true }); setTimeout(() => { p.textContent = "SuperMan"; document.body.append(p); }, 3000);
换成 p 就不灵了.
虽然 p 是有效的, 可以 append to body 看到字, 但是 observer 没有监听到它. 所以监听的节点一定要是 TextNode.
触发时机
和 IntersectionObserver,ResizeObserver 不同。
MutationObserver 触发的很早,它类似 Microtask。
而 IntersectionObserver,ResizeObserver 则是 after ui render。
也合理啦,毕竟 MutationObserver callback 获取的资料比如 addedNodes, removedNode 这些都不需要等 ui render。
window.setTimeout(() => { const container = document.querySelector<HTMLElement>('.container')!; const mo = new MutationObserver( records => console.log(records[0].addedNodes.length > 0 ? 'mutation add' : 'mutation remove'), // 2, 4 ); mo.observe(container, { childList: true, subtree: true }); requestAnimationFrame(() => console.log('rAF')); // 6 const h1 = container.querySelector('h1')!; h1.remove(); console.log('sync'); // 1 queueMicrotask(() => { console.log('micro1'); // 3 container.appendChild(h1); queueMicrotask(() => { console.log('micro2'); // 5 }); }); }, 2000);
效果
相当于,h1.remove 之后它就 queueMicrotask for mutation callback。
没有 unobserve 方法,但有 takeRecords 方法
相关 Issue – Disconnect single target instead of all
不像 IntersectionObserver 和 ResizeObserver 有 unobserve 可以取消指定 element 的监听,
MutationObserver 只有 disconnect 一次性取消所有监听。
workaround 的方案是 wrap 一层,然后自己记入 element,unobserve 时,调用 disconnect 然后在 re-observe 其它的回去😅。参考高赞回复。
另外 MutationObserver 有一个 takeRecords 方法,这个方法用在 disconnect 之前,因为 MutationObserver 触发是 microtask,
所以在同步执行过程中,如果你 disconnect,这时是有可能已经有一些 MutationRecord 即将要发布的,而这个 takeRecords 方法就可以把它们拿出来处理,然后你才 disconnect。