你想知道页面上的某个元素什么时候被偷偷改了吗?比如有个熊孩子脚本悄悄改了你的广告位,或者某个懒加载图片终于加载完了?今天我们就来请一位"卧底"------MutationObserver,让它24小时盯着DOM树,任何变化都逃不过它的眼睛。
前言
假设你开了一家便利店,店里装了监控。你想知道:什么时候有人进来?什么时候货架上的商品被拿走了?什么时候价格标签被换了?普通的监控只能录像,但你需要的是"智能警报"------一有变化就通知你。
这就是MutationObserver的活。它是浏览器提供的一个API,专门用来监听DOM树的变化:节点增删、属性修改、文本内容改变......统统能抓到。而且它不会像setInterval那样一直轮询,性能好得多。
一、MutationObserver是啥?
MutationObserver是一个构造函数,用来创建一个观察者对象。你可以给它指定一个回调函数,然后让它去"盯"某个DOM节点。一旦这个节点或它的子孙节点发生变化,回调函数就会被触发。
js
// 创建一个观察者实例,传入回调
const observer = new MutationObserver((mutationsList, observer) => {
for (let mutation of mutationsList) {
console.log(mutation.type, '发生了变化');
}
});
// 指定要观察的节点
const targetNode = document.getElementById('watch-me');
// 开始观察
observer.observe(targetNode, {
attributes: true, // 观察属性变化
childList: true, // 观察子节点增删
subtree: true, // 观察所有后代节点
characterData: true // 观察文本内容变化
});
// 某天不想观察了
// observer.disconnect();
二、能观察到哪些变化?
配置选项决定了你关心哪些"风吹草动":
attributes:属性变了(比如class、style、src被改)childList:子节点被增删(添加或删除元素、文本节点)characterData:文本节点的内容变了subtree:是否监听后代节点(默认false,只监听目标节点)attributeFilter:只监听特定属性,比如['class', 'src']attributeOldValue:是否记录旧属性值characterDataOldValue:是否记录旧文本值
三、实战:监听广告位有没有被篡改
很多网站会在页面上放广告,但有些恶意脚本会偷偷把广告位换成自己的内容。用MutationObserver可以第一时间发现并报警。
html
<div id="ad-container">
<img src="real-ad.jpg" alt="官方广告">
</div>
js
const adContainer = document.getElementById('ad-container');
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
// 子节点被改了
console.warn('⚠️ 广告位内容被篡改!');
// 可以上报服务器,或者恢复内容
} else if (mutation.type === 'attributes' && mutation.attributeName === 'src') {
console.warn('⚠️ 广告图片被替换了!');
}
});
});
observer.observe(adContainer, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['src', 'href']
});
四、实战:监听输入框内容变化(代替input事件?)
input事件已经能监听输入框变化,但MutationObserver可以监听更底层的文本节点变化,比如通过JS直接修改.value,input事件可能不触发,但MutationObserver可以。
html
<input id="username" type="text">
js
const input = document.getElementById('username');
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
console.log('输入框的值被改了,新值:', input.value);
}
});
});
observer.observe(input, {
attributes: true,
attributeFilter: ['value']
});
注意:这种方式监听value属性变化,只对通过JS设置.value有效,用户手动输入不会触发(因为用户输入不改变value属性,而是改变元素的defaultValue和内部状态)。所以实际中监听输入框还是input事件更合适。这里只是演示能力。
五、实战:监听动态加载的图片,做懒加载
很多懒加载库用IntersectionObserver,但如果你想知道图片什么时候被添加到DOM,可以用MutationObserver。
js
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1 && node.tagName === 'IMG') {
console.log('新图片出现了:', node.src);
// 可以在这里做懒加载初始化
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
六、性能注意事项
MutationObserver虽然比轮询好,但也不能滥用。以下几点要注意:
-
不要观察整个document :如果你
observe(document.body, { subtree: true, childList: true, attributes: true }),那页面上的任何变化都会触发回调,频繁执行可能影响性能。尽量把观察范围缩小到具体容器。 -
回调里不要做太重的操作:MutationObserver的回调是在微任务中执行的,如果里面操作DOM或者计算太多,会阻塞后续渲染。
-
及时disconnect :如果不再需要观察,记得调用
disconnect()释放资源。 -
使用
takeRecords():在disconnect之前,可以调用observer.takeRecords()取出尚未处理的变化记录。
七、与旧API对比:Mutation Events的悲惨往事
很久以前,浏览器有一套Mutation Events(比如DOMNodeInserted、DOMAttrModified等)。它们的问题很多:
- 性能差,每次变化都同步触发,容易导致重入和崩溃
- 不支持批量观察
- 被标记为废弃
MutationObserver是它们的完美替代,异步、批量、性能好。
八、总结:MutationObserver就是你的"鹰眼"
- 它能监听DOM树的各种变化:属性、子节点、文本内容。
- 配置灵活,可以精确到特定属性或是否包含后代。
- 异步回调,批量返回变化记录,性能优秀。
- 应用场景:监听动态内容加载、检测第三方脚本篡改、实现数据绑定(比如某些MVVM库的底层)、与React/Vue的虚拟DOM配合调试等。
有了MutationObserver,你就可以在DOM变化时第一时间响应,像一个隐形的守护者。明天我们将进入Web Storage的世界,看看localStorage、sessionStorage和IndexedDB怎么帮你把数据存到用户浏览器里。
如果你觉得今天的"卧底"够犀利,点个赞让更多人看到。我们明天见!