
元素的样式为何频频被改?
DOM 的属性为何离奇失踪??
消失的 DOM 究竟是何人所为???
出现的陌生 DOM 究竟是人是鬼????
这一切的背后是人性的扭曲还是道德的沦丧?????
是无意写的 Bug 还是有人故意而为之??????
让我们跟随镜头,探寻千变万化的 DOM。
起源
最近在做项目的时候,遇到一个问题:
就是一个很久以前写的页面,里面的代码很乱。而我的任务是将页面的高度与屏幕适配。在部分页面存在着几个 <iframe>
,我需要调整样式使其高度与屏幕适配,但是无论我怎么调整,总会有一个 JavaScript 在不停地修改 <iframe>
的高度,使得它的高度超出屏幕而出现两个滚动条。
由于代码很乱,所以很难找到究竟是哪个代码在捣乱。
Chrome 中的解决方法
在 Chrome 中开发前端是非常开心的一件事情,因为浏览器提供了非常多的调试工具。而我经常用的一个就是:断点调试。
Chrome 提供了一个 DOM 监视的功能,当 DOM 发生变化的时候,自动暂停,这样就能很快定位是谁修改了 <iframe>
的高度了!

在 Elements 页面,选中指定的 DOM 节点,点击最前面的“...”符号(或者使用右键点击),在弹出的菜单中选择“Break On”即可,可以选择多个。
- Subtree Modifications: 当节点树发生变化时
- Attribute Modifications: 当节点属性发生变化时
- Node Removal: 当节点被删除时(包括下级节点)
只要启用其中一个,在具体事件发生的时候,Chrome 就会自动中断到当前执行的脚本代码处。
由于 JavaScript 修改样式无非就是增加/删除 class,或是修改 style 属性,所以,使用 Attribute Modifications
即可找到具体是哪个脚本在修改 DOM 了。
新的问题
在做前端开发的时候,经常会想要监听 DOM 节点的变化。当 DOM 变化的时候,触发一系列事件。
一般来说,使用轮询的方式可以非常简单地解决这个问题,就是使用 setInterval
来不断地检查 DOM 是否发生了变化。这种方式简单粗暴,但是会遇到两个问题:时间间隔设置过长,DOM 变化响应不够及时;时间间隔设置过短,不仅浪费 CPU,而且可能出现卡顿。
当然,也可以使用 requestAnimationFrame
来做,原理其实和 setInterval
一样,只不过在一定程度上可以得到 DOM 变化实时响应,但是依旧是导致 CPU 运行时间片的浪费。
有没有更好的办法呢?
在旧版 DOM Events 标准中,有一个 Mutation events
,可以用来监听 DOM 的变化,在 DOM 变化的时候触发事件。
在 DOM3 中定义了 9 种 Mutation 事件:DOMAttrModified
、DOMAttributeNameChanged
、DOMCharacterDataModified
、DOMElementNameChanged
、DOMNodeInserted
、DOMNodeInsertedIntoDocument
、DOMNodeRemoved
、DOMNodeRemovedFromDocument
、DOMSubtreeModified
。
这 9 种事件可以直接通过 element.addEventListener
添加到 DOM 元素上。
但是,Mutation 事件已经被反对使用!并且从 Web 标准事件中删除了!
由于性能问题,Mutation 事件会导致 DOM 修改的性能降低 1.5~7 倍,并且不能通过移除事件来恢复性能。
并且这个事件在各个浏览器上的实现也存在差异。
所以,DOM4 开始,推荐使用 Mutation Observers
来代替 Mutation events
。
Mutation Observers
Mutation Observer API 可以用来监视 DOM 的变化,包括属性的变化、节点的增减、内容的变化等。
比 Mutation Events 高在哪里?
为什么 Mutation Observers
要比 Mutation Events
好?
由于 Mutation Events
是监视到 DOM 发生变化时产生的事件,它会在任何一个 DOM 发生变化的时候立刻被触发。并且,由于事件是同步进行的,所以如果 DOM 的变化较多,就会产生大量的事件回调,导致严重的性能问题。
而 Mutation Observers
虽然和 Mutation Events
很像,但是 Mutation Observers
不是事件,它是异步触发的,并且不是每次 DOM 变动都会触发,而是会等待多次 DOM 变动完成后一次性触发,使用一个数组来记录 DOM 变动的步骤。这样一来,即使是频繁的 DOM 操作,对性能的影响也不会有多明显。
举个例子,我现在需要将一篇包含 1000 个段落的文章显示到页面上,也就是要往页面中插入 1000 个 <p></p>
。
如果使用 Mutation Events
的话,这时就会产生 1000 个 DOMNodeInserted
事件;而如果使用 Mutation Observers
就不一样了,它只会触发一次,得到一个数组,包含了 1000 个插入节点的信息。
怎么用呢?
MutationObserver
是一个构造函数,可以使用 new
来创建一个 MutationObserver
的实例。这个构造函数接受一个回调函数作为参数,也就是每次 Mutation Observers
触发时调用的函数,函数接受两个参数,第一个参数是 MutationRecord
数组,用于存储 DOM 的变化记录,第二个参数是 MutationObserver
实例本身。
MutationObserver
的实例有 3 个成员方法:observe
、disconnect
、takeRecords
。
observe
用于注册监听器,接受两个参数,第一个参数是要监听的节点,第二个参数是监听的配置。
监听配置是一个对象,可以有 childList
、attributes
、characterData
、subtree
、attributeOldValue
、characterDataOldValue
、attributeFilter
,要监听哪种变化,只需要将对应的属性设置为 true
即可,其中 childList
、attributes
、characterData
三者必须至少出现一个。
属性 | 数据类型 | 描述 |
---|---|---|
childList | boolean | 观察目标增加或移除了子节点 |
attributes | boolean | 观察目标增加、删除或修改了某个属性 |
characterData | boolean | (目标为 characterData 节点时有效,包括文本节点、注释节点、处理指令节点等)文本内容发生了变化 |
subtree | boolean | 不仅监视 ovserve 第一个参数指定的观察目标,同时监视所有的下级节点 |
attributeOldValue | boolean | 在监视 attributes 的时候,属性发生变化后是否要记录变化前的内容 |
characterDataOldValue | boolean | 在监视 characterData 的时候,文本内容发生变化后是否要记录变化前的内容 |
attributeFilter | Array<string> | 一个属性名数组,可以用于过滤 attributes 的变化 |
注册成功后,构造函数里提供的回调函数将会被调用,第一个参数就得到了变化数组。变化对象的结构包含以下属性:
属性 | 数据类型 | 描述 |
---|---|---|
type | String | 变化类型,对应监听配置对象中的 childList 、attributes 、characterData |
target | Node | 变化的目标节点,如果 type 是 attributes 或 characterData ,则 target 为变化节点,否则为变化节点的父节点 |
addedNodes | NodeList | 被添加的节点列表(可能为 null ) |
removedNodes | NodeList | 被删除的节点列表(可能为 null ) |
previousSibling | Node | 被添加或被删除的节点的前一个兄弟节点(可能为 null ) |
nextSibling | Node | 被添加或被删除的节点的后一个兄弟节点(可能为 null ) |
attributeName | String | 变化的属性名称(可能为 null ) |
attributeNamespace | String | 变化的属性所在的 XML 命名空间(可能为 null ) |
oldValue | String | 如果 type 是 attributes 或 characterData ,则 oldValue 为变化前的值,否则为 null |
disconnect
用于停止监听。
takeRecords
用于清空并返回当前 MutationObserver
记录的 DOM 变化步骤。
浏览器兼容性

可以看到,兼容性还是非常好的,可以放心使用。
总结
MutationObserver
提供了比 Mutation Events
更高效、更灵活的 DOM 监视方案,可以根据自己的需要自定义监视对象,在组件化项目中可以发挥更大的价值——不需要组件内部提供接口,就可以收到组件内容变化的通知。
但是,MutationObserver
虽好,可不要滥用哦!
Chrome 提供的 Break On
功能看起来就像是 MutationObserver
的精简版,非常实用。
关注微信公众号:创宇前端(KnownsecFED),码上获取更多优质干货!
