前言
MutationObserver 是一个dom观察器,当dom发生改变前(willchanged),会执行其回调函数,自己观察会发现不少三方UI组件用到了
其会将回调添加到当前执行栈的末尾,且在其他异步任务之前执行((如I/O、计时器等)),对于高性能低延迟的场景非常好用,也避免了延迟调用出现的卡顿问题
MutationObserver
MutationObserver 创建观察很简单,直接 new MutationObserver 即可
new MutationObserver 创建监听回调
会在观察内容发生改变操作前,回调该函数,因为打印时会发件 target 节点的 childNodes 节点没有变化
第一个参数为变化数组,会按照顺序记录先后的改变操作(增删等),observer为我们的观察对象,可以取消、观察
let obser = new MutationObserver(
(mutations: MutationRecord[], observer: MutationObserver) => {
console.log(mutations);
}
);
回调返回值大致结构如下所示,主要用到第一个参数,第二个参数后面介绍,可用可不用
observer 开启监听,设置监听规则
通过调用其 observer 函数来启用监听,可以设置 config 参数,一般用 childList、subtree、characterDataOldValue 就足够了,参数使用如下所示
const dom = document.getElementById("pppp");
obser.observe(dom!, {
childList: true
});
config 属性如下所示,根据自己需要取即可
/** 设置一个属性本地名(不包含命名空间)的列表,如果并非所有属性更改都需要被观察,并且 attributes 为 true 或被省略。 */
attributeFilter?: string[];
/** 设置为 true,如果 attributes 为 true 或被省略,并且需要记录目标属性更改前的值。 */
attributeOldValue?: boolean;
/** 设置为 true,如果要观察目标属性的更改。如果指定了 attributeOldValue 或 attributeFilter,则可以省略此属性。 */
attributes?: boolean;
/** 设置为 true,如果要观察目标数据的更改。如果指定了 characterDataOldValue,则可以省略此属性。 */
characterData?: boolean;
/** 设置为 true,如果 characterData 为 true 或被省略,并且需要记录目标更改前的数据。 */
characterDataOldValue?: boolean;
/** 设置为 true,如果要观察目标子节点的更改。 */
childList?: boolean;
/** 设置为 true,如果要观察不只是目标,还有目标后代的更改。 */
subtree?: boolean;
disconnect取消监听
通过 disconnect 取消监听,有时为了避免不必要的监听,可以在合适的时机取消监听,在重新开启监听
obser.disconnect()
案例(让被删除节点恢复案例,部分水印防伪功能)
使用 MutationObserver 监听一个 pppp 的节点,检测到其删除操作后,我们给某个节点主动添加回来,一些水印防伪功能会用到,我们就做一个简易的防删除案例
//查找dom,用于监听其子节点
const dom = document.getElementById("pppp");
//设置改变前的回调
const obser = new MutationObserver(
(mutations: MutationRecord[], observer: MutationObserver) => {
mutations.forEach((node) => {
node.removedNodes.forEach((node) => {
dom?.appendChild(node);
});
});
}
);
//添加回调
obser.observe(dom!, {
childList: true
});
这样完成了一个删除后的恢复功能,也可以根据需要调用 disconnect、observe方法,如果不想由于回调内部操作再次触发回调,需要这样做
例如:上面的删除后的追加操作,如果里面是不是单纯追加,而是重置操作,可能还有删除和追加操作,则再次删除操作可能会触发回调地狱,需要额外添加判断条件,如果使用 disconnect + observe则可以有效避免
此外,可能还会有很多情况,有时需要在操作的末尾,可能还会有其他dom操作,要延迟恢复监听的话,可以通过setTimeout 重新启动一个宏队列任务避免提前开启监听
毕竟其回调会被追加到当前执行栈的末尾,可以通过 setTimeout 会重新启动一个宏队列任务(优先级在执行中的微队列任务之后),以此解决监听时机问题
const obser = new MutationObserver(
(mutations: MutationRecord[], observer: MutationObserver) => {
observer.disconnect();
...我们的操作
observer.observe(dom!, {
childList: true,
});
//开启一个新的
// setTimeout(() => {
// observer.observe(dom!, {
// childList: true,
// });
// }, 0);
}
);
整体代码
function App() {
useEffect(() => {
const dom = document.getElementById("pppp");
//改变前的回调
const obser = new MutationObserver(
(mutations: MutationRecord[], observer: MutationObserver) => {
console.log(mutations);
observer.disconnect();
mutations.forEach((node) => {
node.removedNodes.forEach((node) => {
dom?.appendChild(node);
});
});
observer.observe(dom!, {
childList: true,
});
// setTimeout(() => {
// observer.observe(dom!, {
// childList: true,
// });
// }, 0);
}
);
obser.observe(dom!, {
childList: true,
});
}, []);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p id="pppp">
Edit <code>src/App.tsx</code> and save to reload.
</p>
</header>
</div>
);
}
最后
自己也可以尝试将添加的文本信息换个颜色🤣