应精准监听高危广告容器节点而非整个body,用querySelector定位data-ad-container等稳定属性,动态节点需先监听body再切换目标;过滤addedNodes中非元素节点,用matches匹配广告特征并深入查询子节点;attributeFilter限定class/style/hidden,修复前断开observer,优先textContent清空或setAttribute标记移除。

能实时过滤,但必须放弃“监听整个 body”这种懒办法——否则不是漏报就是自己先被拖垮。
只 observe 真正高危的容器节点
第三方广告脚本不会随机往 document.body 里撒节点,它们有固定落点:比如 #ad-banner、.sidebar-ads、[data-ad-slot]。盲目监听 document.body 会捕获 Vue/React 的 diff 更新、浏览器扩展注入、甚至你自己加的调试节点,回调频次飙升,CPU 占用直线上升。
应该这样做:
- 用
document.querySelector精准定位已知广告挂载点,优先选带data-前缀的稳定属性(如data-ad-container),避开含哈希或时间戳的 class - 若容器动态生成(如 SPA 路由切换后才出现),先监听
document.body一次,等目标节点出现后立即observer.disconnect(),再换绑到该节点上 - 多个广告区就建多个
MutationObserver实例,别塞进一个回调里硬扛
childList + subtree 必开,但 addedNodes 要严格过滤
广告注入最常见手法是 appendChild、insertAdjacentHTML 或 documentFragment 批量挂载,这些都会触发 childList 类型变更;而 subtree: true 是为了捕获深层嵌套的广告(比如客服浮窗里又嵌了个 taboola 推荐栏)。
但 mutation.addedNodes 里混着文本节点、注释节点、甚至你自己的修复节点——不筛干净就会误删、死循环:
- 遍历前先用
node.nodeType === Node.ELEMENT_NODE过滤,跳过所有TEXT_NODE(值为 3)和COMMENT_NODE(值为 8) - 用
node.matches()匹配高危特征:script[src*="ad"]、iframe[src*="taboola"]、[id^="google_ads"]、.zalo-ads - 对疑似节点再向内查一层:
node.querySelector('img[alt*="advert"]')或node.querySelector('[data-track="ad-click"]'),比只看外层 class 更可靠
attributes 监听要设 attributeFilter,避免 style/class 滥用
很多广告 SDK 不直接插 DOM,而是偷偷改已有节点的 style(设 display: none 隐藏原文)或 class(加 hidden-ad 类伪装)。但全开 attributes: true 会让每个 class 切换都进回调,性能损失超 30%。
正确做法是:
- 只监听关键属性:
attributes: true+attributeFilter: ['class', 'style', 'hidden'] - 在回调里检查
mutation.attributeName是否为'style',再读getComputedStyle(mutation.target).display,而不是依赖mutation.target.style.display(可能为空) - 遇到
class变更,用mutation.target.classList.contains('ad-hide')判断,别用字符串匹配(易误伤)
修复操作必须断开观察器,且避免 replaceWith 引发重绘抖动
在回调里直接调 node.remove() 或 node.replaceWith() 会再次触发 MutationObserver,形成死循环。更糟的是,replaceWith 会强制重绘整段 DOM,页面卡顿明显。
安全执行的关键是:
- 进回调第一句就
observer.disconnect(),修复完再observer.observe(target, config)重新绑定 - 优先用
node.textContent = ''清空文本类广告,比innerHTML = ''更轻量 - 对 iframe/script 类节点,用
node.setAttribute('data-blocked', 'true')标记后移除,方便后续审计日志追溯
真正难的不是写几行 MutationObserver,而是判断「这个节点到底是不是广告」——它可能没 class、没 id、src 是 base64 编码的,甚至藏在 shadow DOM 里。这时候靠规则已经不够,得结合首次加载时的 DOM 快照做 diff,或者引入轻量级 ML 特征(比如节点深度、子元素密度、文本熵值),但那是另一层问题了。










