应精准监听法律声明容器节点,仅启用childList、subtree、attributes(限定class/style/hidden)、characterData,禁用旧值比对;回调中验证目标归属、过滤恶意节点、检查隐藏属性与文本篡改,修复前断开观察或延时执行,防止死循环。
必须限定观察范围,不能监听 document.body 或整个 document——这会捕获大量无关变更,导致误报、性能下降,甚至被恶意脚本利用回调逻辑制造死循环。真实场景中,法律声明通常位于固定容器内,比如 id="legal-notice" 或 class="copyright-statement"。
正确做法是:先用 document.querySelector 精准获取该容器节点,再传给 MutationObserver 实例:
const noticeEl = document.querySelector('#legal-notice') || document.querySelector('.copyright-statement');if (!noticeEl) return; // 容器不存在,不启动监控
注意:不要依赖第三方插件动态生成的 class 名(如含时间戳或哈希值),优先使用语义化、稳定不变的 id 或带 data- 前缀的属性锚定。
法律声明被篡改,常见手法是替换文本、删除节点、注入广告标签、修改 style 隐藏原文。不需要监听所有变化,应关闭冗余选项:
childList: true —— 必开,用于捕获 <div> 被替换成广告 <iframe> 或整段被 remove()
subtree: true —— 必开,确保子树内任意层级的篡改(如嵌套 <span> 里插入水印)都能被捕获attributes: true + attributeFilter: ['class', 'style', 'hidden'] —— 限制只关注可能用于隐藏/伪装的属性characterData: true —— 必开,防止直接修改文本节点内容(例如把“版权所有”改成“授权转载”)attributeOldValue: false、characterDataOldValue: false —— 不需要比对旧值,只需触发即响应省略 subtree 会导致深层篡改完全漏报;开启 attributes 却不设 attributeFilter 会平白增加 30%+ 回调开销。
回调函数不是日志记录器,它要能区分“用户正常编辑”和“脚本批量注入”。关键判断逻辑必须轻量且可靠:
mutation.target 是否属于声明容器或其后代(用 noticeEl.contains(mutation.target))mutation.type === 'childList',遍历 mutation.addedNodes,过滤出含 script、iframe、ad-banner、taboola- 等特征的节点mutation.type === 'attributes' 且 mutation.attributeName === 'style',检查 mutation.target.style.display === 'none' 或 visibility === 'hidden'
mutation.type === 'characterData',用正则粗筛是否包含关键词丢失(如“©”、“版权所有”、“保留所有权利”消失)一旦确认非法,立即执行修复:用 noticeEl.replaceWith(noticeEl.cloneNode(true)) 恢复原始结构,避免 innerHTML 引发事件重绑或脚本重执行。
这是最容易翻车的环节。在回调里直接调用 noticeEl.remove() 或 noticeEl.textContent = ...,会再次触发 MutationObserver,形成无限循环,最终卡死页面。
安全做法只有两种:
observer.disconnect(),修复完成后再 observer.observe(...)
setTimeout 延迟修复(延迟 0ms 即可),让当前批 mutation 处理完再操作 DOM,避开同一轮事件循环别忘了加 try/catch 包裹整个回调体——恶意脚本可能故意抛错中断你的监控逻辑,而你无法控制它的行为。
真正难的是 iframe 注入和 document.write 场景,MutationObserver 对这两者完全无感,得靠 window.addEventListener('load', ...) 和 iframe.onload 补位检测。