MutationObserver 必须替代 Mutation Events,因其兼容性差(IE9 不支持 DOMSubtreeModified、WebKit 不认 DOMAttrModified)、性能差(同步触发致主线程卡死)、逻辑失控(冒泡不可控、易无限循环);配置需精准(如 childList+subtree 组合)、回调需谨慎处理批量记录、disconnect/takeRecords 必须显式调用以防内存泄漏。
替代
不是因为“更时髦”,而是因为后者在真实项目里根本跑不起来——兼容性碎成渣、性能一碰就卡、逻辑还容易失控。
为什么
在现代项目中必须被弃用
直接看事实:
这类事件在 IE9 完全不支持;WebKit(Safari)不认
;Firefox 拒绝实现
和
。你写一套监听逻辑,三台浏览器跑出三种行为,调试成本远高于功能本身。
更致命的是同步执行机制:每次 DOM 变动(哪怕只是连续插入 10 个
),都会立刻触发事件、进回调、再冒泡——中间穿插 JS 执行、重排重绘,主线程直接被锁死。实测中,频繁操作下页面帧率掉到 5fps 是常态。
常见错误现象包括:
触发时,目标节点的
还是
(因为样式尚未计算)
嵌套监听时,父节点的
和子节点的同名事件互相触发,形成无限循环
使用
无法阻止冒泡传播,因为规范没定义它对 Mutation Events 的作用
的 observe 配置怎么选才不翻车
配置不是“全开就完事”。开多了浪费性能,开少了收不到关键变化。核心原则是:只监听你真正在意的变动类型 + 明确作用域边界。
立即学习
“
前端免费学习笔记(深入)
”;
典型误配:
只设
却漏掉
→ 新增的孙子节点完全不触发
监听
但没加
→ 回调里拿不到旧值,没法做 diff 判断
用
却忘了 class 变动也属于
类型,不打开
开关,过滤器压根不生效
推荐最小安全配置组合:
使用HTML,CSS,JavaScript开发Android应用程序 英文文字pdf版附源文件
如果你了解HTML,CSS和JavaScript,您已经拥有所需的工具开发Android应用程序。本动手本书展示了如何使用这些开源web标准设计和建造,可适应任何Android设备的应用程序 - 无需使用Java。您将学习如何创建一个在您选择的平台的Android友好的网络应用程序,然后转换与自由PhoneGap框架到一个原生的Android应用程序。了解为什么设备无关的移动应用是未来的潮流,并开始构建应用程序,提供更
下载
监听子元素增删:
监听特定属性(如
):
监听文本内容变更(如富文本编辑器):
回调里处理
的几个硬坑
的回调参数是
数组,每项是只读的
。它不是“每次变动一条记录”,而是浏览器按微任务批量合并后的结果——这点极易误判。
常见陷阱:
认为
就代表只新增了一个节点 → 实际可能是
插入,
里是多个节点
直接遍历
并调用
→ 节点已从 DOM 移除,再调用会报错
在回调里修改被观察的 DOM(比如给新增节点加 class)→ 可能触发新一轮观察,若没控制好,导致无限递归
稳妥做法:
先用
转成真数组再遍历
检查
值(
/
/
)再分支处理
如需响应式操作,优先用
延迟到下一帧,避免干扰当前批处理
disconnect() 和 takeRecords() 不是可选项,是必选项
很多人只调
,却忘了清理。后果是:组件卸载后 observer 仍持有 DOM 节点引用,造成内存泄漏;或者旧 observer 没停,新 observer 又起,同一变动触发两次回调。
必须在组件销毁、路由离开、或不再需要监听时显式调用。而
的真实用途常被忽略——它用于“清空未派发的记录”,典型场景是单元测试中验证 observer 是否收到了预期变更,或在高频渲染中手动取数避免回调堆积。
容易被忽略的细节:
后 observer 实例仍存在,可再次
,不是销毁对象
返回的是当前积压的所有记录,调用后内部队列清空,但不会触发回调
如果监听目标节点已被
,observer 不会自动失效,必须手动
真正难的从来不是写对那一行
,而是理解它背后那套微任务调度、批量合并、弱引用管理的约束条件——这些不摸清,代码上线后出问题,连日志都看不出因果。
MutationObserverMutation EventsMutation EventsDOMSubtreeModifiedDOMAttrModifiedDOMElementNameChangedDOMAttributeNameChangedliDOMNodeInsertedoffsetHeight0DOMSubtreeModifiedevent.stopPropagation()MutationObserverchildList: truesubtree: trueattributes: trueattributeOldValue: trueattributeFilter: ['class']attributesattributes{ childList: true, subtree: true }data-status{ attributes: true, attributeFilter: ['data-status'], attributeOldValue: true }{ characterData: true, characterDataOldValue: true, subtree: true }MutationRecordMutationObservermutationsMutationRecordmutation.addedNodes.length === 1documentFragmentaddedNodesmutation.removedNodes.remove()Node.removeChild: The node to be removed is not a child of this nodeArray.from(mutation.addedNodes)mutation.type"childList""attributes""characterData"requestAnimationFrameobserve()disconnect()takeRecords()disconnect()observe()takeRecords()remove()disconnect()new MutationObserver(...)