MutationObserver
是用于代替 MutationEvents
作为观察 DOM
树结构发生变化时,做出相应处理的 API
。为什么要使用 MutationObserver
去代替 MutationEvents
呢,我们先了解一下 MutationEvents
MutationEvents
它简单的用法如下:
1 | document.getElementById('list').addEventListener( |
1 | // Mutation 事件列表 |
其中 DOMNodeRemoved
,DOMNodeInserted
和 DOMSubtreeModified
分别用于监听元素子项的删除,新增,修改(包括删除和新增),DOMAttrModified
是监听元素属性的修改,并且能够提供具体的修改动作。
Mutation Events 遇到的问题
- IE9 不支持
MutationEvents
。Webkit 内核不支持DOMAttrModified
特性,DOMElementNameChanged
和DOMAttributeNameChanged
在 Firefox 上不被支持。 - 性能问题 1.
MutationEvents
是同步执行的,它的每次调用,都需要从事件队列中取出事件,执行,然后事件队列中移除,期间需要移动队列元素。如果事件触发的较为频繁的话,每一次都需要执行上面的这些步骤,那么浏览器会被拖慢。 2.MutationEvents
本身是事件,所以捕获是采用的是事件冒泡的形式,如果冒泡捕获期间又触发了其他的MutationEvents
的话,很有可能就会导致阻塞 Javascript 线程,甚至导致浏览器崩溃。
Mutation Observer
MutationObserver
是在 DOM4
中定义的,用于替代 MutationEvents
的新 API,它的不同于 events 的是,所有监听操作以及相应处理都是在其他脚本执行完成之后异步执行的,并且是所以变动触发之后,将变得记录在数组中,统一进行回调的,也就是说,当你使用 observer
监听多个 DOM 变化时,并且这若干个 DOM 发生了变化,那么 observer
会将变化记录到变化数组中,等待一起都结束了,然后一次性的从变化数组中执行其对应的回调函数。
特点
- 所有脚本任务完成后,才会运行,即采用异步方式
- DOM 变动记录封装成一个数组进行处理,而不是一条条地个别处理 DOM 变动。
- 可以观察发生在 DOM 节点的所有变动,也可以观察某一类变动
目前,Firefox(14+)、Chrome(26+)、Opera(15+)、IE(11+) 和 Safari(6.1+) 支持这个 API。 Safari 6.0 和 Chrome 18-25 使用这个 API 的时候,需要加上 WebKit 前缀(WebKitMutationObserver)。可以使用下面的表达式检查浏览器是否支持这个 API。
1 | var MutationObserver = |
如何使用 MutationObserver
在应用中集成 MutationObserver
是相当简单的。通过往构造函数 MutationObserver
中传入一个函数作为参数来初始化一个 MutationObserver
实例,该函数会在每次发生 DOM 发生变化的时候调用。MutationObserver
的函数的第一个参数即为单个批处理中的 DOM 变化集。每个变化包含了变化的类型和所发生的更改。
1 | var mutationObserver = new MutationObserver(function(mutations) { |
创建的实例对象拥有三个方法:
observe
-开始进行监听。接收两个参数-要观察的 DOM 节点以及一个配置对象。disconnect
-停止监听变化。takeRecords
-触发回调前返回最新的批量 DOM 变化。
observer 方法
observer 方法指定所要观察的 DOM 元素,以及要观察的特定变动。
1 | var article = document.querySelector('article') |
上面代码分析:
- 指定所要观察的 DOM 元素 article
- 指定所要观察的变动是子元素的变动和属性变动。
- 将这两个限定条件作为参数,传入
observer
对象observer
方法。
disconnect 方法
- disconnect 方法用来停止观察。发生相应变动时,不再调用回调函数。
1 | var MutationObserver = |
takeRecord 方法
takeRecord 方法用来清除变动记录,即不再处理未处理的变动。
在观察者对象上调用 takeRecords
会返回 其观察节点上的变化记录(MutationRecord)数组。其中 MutationRecord
数组也会作为,观察者初始化时的回调函数的第一个参数。
其包含的属性如下:
- type 如果是属性发生变化,则返回
attributes
.如果是一个CharacterData
节点发生变化,则返回characterData
,如果是目标节点的某个子节点发生了变化,则返回childList
. - target 返回此次变化影响到的节点,具体返回那种节点类型是根据 type 值的不同而不同的,如果 type 为
attributes
,则返回发生变化的属性节点所在的元素节点,如果 type 值为characterData
,则返回发生变化的这个 characterData 节点.如果 type 为childList
,则返回发生变化的子节点的父节点. - addedNodes 返回被添加的节点
- removedNodes 返回被删除的节点
- previousSibling 返回被添加或被删除的节点的前一个兄弟节点
- nextSibling 返回被添加或被删除的节点的后一个兄弟节点
- attributeName 返回变更属性的本地名称
- oldValue 根据 type 值的不同,返回的值也会不同.如果 type 为 attributes,则返回该属性变化之前的属性值.如果 type 为 characterData,则返回该节点变化之前的文本数据.如果 type 为 childList,则返回 null
1 | observer.takeRecord() |
MutationObserver 类型
MutationObserver
所观察的 DOM 变动(即上面代码的 option 对象),包含以下类型:
- childList:子元素的变动
- attributes:属性的变动
- characterData:节点内容或节点文本的变动
- subtree:所有下属节点(包括子节点和子节点的子节点)的变动
想要观察哪一种变动类型,就在 option 对象中指定它的值为 true。
需要注意的是,不能单独观察 subtree 变动,必须同时指定 childList、attributes 和 characterData 中的一种或多种。
除了变动类型,option 对象还可以设定以下属性:
- attributeOldValue:值为 true 或者为 false。如果为 true,则表示需要记录变动前的属性值。
- characterDataOldValue:值为 true 或者为 false。如果为 true,则表示需要记录变动前的数据值。
- attributesFilter:值为一个数组,表示需要观察的特定属性(比如[‘class’, ‘str’])。
创建 MutationObserver
并 获取 dom 元素,定义回调数据。
1 | // 获取MutationObserver,兼容低版本的浏览器 |
- 子元素的变动
1 | Observer.observe(list, { |
- 监测 characterData 的变动
1 | Observer.observe(list, { |
- 监测属性的变动
1 | Observer.observe(list, { |
- 属性变动前,记录变动之前的值
1 | Observer.observe(list, { |
- characterData 变动时,记录变动前的值。
1 | Observer.observe(list, { |
- attributeFilter {Array} 表示需要观察的特定属性 比如 [‘class’, ‘src’];
1 | Observer.observe(list, { |
案例分析—demo 编辑器
下面我们做一个简单的 demo 编辑器:
- 首先给父级元素
ol
设置contenteditable
让容器可编辑; - 然后构造一个
observer
监听子元素的变化; - 每次回车的时候,控制台输出它的内容;
1 | <div id="demo"> |
1 | const MutationObserver = |
现在我们继续可以做一个类似于 input 和 textarea 中的 valueChange
的事件一样的,监听值变化,之前的值和之后的值,如下代码:
1 | const MutationObserver = |
注意: 对 input 和 textarea 不起作用的。
案例分析—编辑器统计字数
1 | <div |
1 | const MutationObserver = |