监听动态插入的广告或第三方脚本节点必须使用MutationObserver并配置{childList:true,subtree:true}监听document.body等稳定父容器,避免漏监深层节点;需过滤元素节点、防止空指针、精确指定attributeFilter及启用attributeOldValue;组件卸载时须手动调用observer.disconnect()防内存泄漏。

监听动态插入的广告或第三方脚本节点
很多页面在加载后由广告 SDK 或埋点脚本主动 appendChild 或 insertAdjacentHTML 插入 DOM,这类节点不会出现在初始 HTML 中,靠 DOMContentLoaded 或 load 事件根本捕获不到。用 MutationObserver 是唯一可靠方式。
关键点在于观察范围要设对:必须用 { childList: true, subtree: true },否则只监听一级子节点变化,深层插入(比如广告嵌套在 #main > .content > .ad-slot)会漏掉。
常见错误是把 observer 实例挂载到太深的容器上——比如只监听 .ad-slot,但该节点本身是后来才创建的。正确做法是监听 document.body 或已知稳定存在的父容器(如 #app)。
示例:
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1 && node.matches && node.matches('.ad-banner, [data-ad]')) {
console.log('检测到广告节点:', node);
// 这里可触发拦截、打标、上报等逻辑
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
避免重复执行回调的典型陷阱
MutationObserver 的回调是异步批量触发的,但一个 mutation 可能包含多个 addedNodes 或 removedNodes,而每个 Node 对象又可能有 childNodes。如果没做类型和存在性判断,很容易报错或误处理。
容易踩的坑包括:
- 直接对
node.textContent操作,但node是Text节点(nodeType === 3),没有matches方法 - 遍历
addedNodes时用了for...in,结果遍历了 NodeList 的原型方法 - 没检查
node.parentElement就调用node.parentElement.classList.contains(...),导致空指针
实操建议:
- 始终用
node.nodeType === 1过滤出元素节点 - 用
Array.from(mutation.addedNodes)转成数组再filter/forEach - 需要匹配祖先时,用
node.closest('.trigger-area')替代手动向上查父级
监听特定属性变更(如 class、src、data-*)
当业务依赖某个元素的 class 切换(例如 loading 状态)、src 动态替换(图片懒加载完成)、或自定义 data-status 更新时,attributeFilter 比轮询高效得多。
注意:仅设置 attributes: true 默认监听所有属性,性能差且干扰多;务必配合 attributeFilter: ['class', 'src', 'data-status'] 精确指定。
另一个易忽略点:attributeOldValue: true 必须显式开启,否则 mutation.oldValue 是 undefined,无法对比前后值。
示例(监听按钮禁用状态变化):
const btnObserver = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'attributes' && mutation.attributeName === 'disabled') {
const wasDisabled = mutation.oldValue === '' || mutation.oldValue === 'true';
const isDisabled = mutation.target.hasAttribute('disabled');
console.log(`按钮状态从 ${wasDisabled} → ${isDisabled}`);
}
});
});
btnObserver.observe(document.querySelector('#submit-btn'), {
attributes: true,
attributeFilter: ['disabled'],
attributeOldValue: true
});
observer.disconnect() 不及时导致内存泄漏
长期运行的单页应用中,如果组件卸载(如 Vue 的 beforeUnmount、React 的 useEffect cleanup)时没调用 observer.disconnect(),observer 会持续持有对 DOM 节点的引用,阻止垃圾回收——尤其当监听的是 document.body 这种全局节点时,整个组件树都可能被意外保留在内存中。
实操要点:
- 不要依赖
observer实例自动销毁;它不会随 DOM 节点移除而自动停用 - 在组件销毁、路由离开、或明确不再需要监听时,必须手动调用
observer.disconnect() - 可以加一层保护:调用前检查
observer.takeRecords().length是否为 0,避免重复 disconnect 报错
真正麻烦的是那些监听了全局节点、又没绑定到具体生命周期的脚本——它们往往藏在工具函数里,上线后才被内存分析工具揪出来。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











