Mutation Observer(变动观察者)
定义
Mutation Observer
是一种JavaScript API,用于异步监测DOM树的变动,包括元素的添加、删除、属性变化等。当DOM发生变动时,它可以触发回调函数,允许你对变动作出响应。
Mutation Observer的意义
Mutation Observer API 的设计意义就是用来替换掉在 DOM 3 事件规范中引入的 Mutation events。
你可能会问了为什么呢?
这就要讲讲Mutation events了
先给大家看一Mutation events的简单用法
上面这段代码乍一看可能没什么问题,确实也能实现监听改变的功能,但是往深层次考虑它还是存在很多缺点。例如:
-
兼容性问题:
- IE9 不支持 Mutation Events:Mutation Events在较旧版本的Internet Explorer(如IE9及更早版本)中不受支持。这意味着如果我们使用IE9的浏览器运行这段代码,将会失效。
- Webkit 内核不支持 DOMAttrModified:Webkit内核浏览器(如旧版本的Safari和Chrome)不支持
DOMAttrModified
属性的Mutation Event。 - Firefox 不支持 DOMElementNameChanged 和 DOMAttributeNameChanged:Mozilla Firefox不支持
DOMElementNameChanged
和DOMAttributeNameChanged
这两种Mutation Event。
-
性能问题:
- 同步执行:Mutation Events是同步执行的,它们会在DOM发生变化时立即触发,从事件队列中取出事件,执行事件处理程序,然后从队列中移除。如果DOM变化频繁,每次事件都需要执行这些操作,如果产生堵塞,将会引起全局堵塞
- 事件冒泡:Mutation Events使用事件冒泡来传播事件。如果在事件冒泡过程中触发其他Mutation Events,可能会导致事件处理程序的嵌套,这可能会占用大量的JavaScript执行时间,甚至导致浏览器线程阻塞。
Mutation Observer的出现就是为了代替Mutation events,来弥补Mutation events的缺陷。而且Mutation Events能监听的DOM树结构变化,Mutation Observer都能监听。
DOMAttrModified
:当元素的属性被修改时触发,我们可以捕获属性值的更改。DOMAttributeNameChanged
:当元素的属性节点名称发生变化时触发,可以检测到属性名称的修改。DOMCharacterDataModified
:当文本节点的内容发生变化时触发,用于监视文本节点中的文本内容变化。DOMElementNameChanged
:当元素的节点名称发生变化时触发,用于检测节点名称的修改。DOMNodeInserted
:当元素的子节点被插入到DOM树中时触发,我们可以监测到新节点的添加。DOMNodeRemoved
:当元素的子节点被从DOM树中移除时触发,用于检测节点的移除。DOMNodeInsertedIntoDocument
:当元素的子节点被插入到文档中时触发,用于监视节点的插入操作。DOMSubtreeModified
:当DOM树的子树结构发生变化时触发,我们可以捕获到更广泛的DOM树变化。
Mutation Observer的优点
兼容性好
- 实时监测DOM变动:可以实时监测DOM树的变动,而不需要轮询或事件监听器。
- 灵活性:可以精确地指定要观察的DOM元素,以及要观察的变动类型。
- 性能优好:它通过异步执行回调函数来处理DOM变化,不会阻塞主线程。
用法
1.创建Mutation Observer实例
首先,我们需要创建一个Mutation Observer的实例。它需要一个回调函数作为参数,这个回调函数会在DOM发生变动时被触发。
const observer = new MutationObserver(callback);
2.指定观察的目标元素和选项
observe(target, options) :observe方法的作用是启动监听,它接受两个参数。
- 第一个参数:所要观察的 DOM 节点
- 第二个参数:一个配置对象,指定所要观察的特定变动
然后,我们需要指定要观察的目标元素和观察选项。观察选项是一个配置对象,它包括以下属性:
childList
:“boolean”(当目标元素的子元素发生添加或删除时触发回调。)attributes
:“boolean”(当目标元素的属性发生变化时触发回调。)subtree
:“boolean”(是否观察目标元素的子孙元素。)characterData
:“boolean”(当目标元素的文本内容发生变化时触发回调。)attributeOldValue
:“boolean”,(表示观察attributes
变动时,是否需要记录变动前的属性值。)characterDataOldValue
:“boolean”(表示观察characterData
变动时,是否需要记录变动前的值。)attributeFilter
:“Array”(一个数组,包含要观察的特定属性的名称。)
const target = document.querySelector('#elementToObserve');
const config = {
childList: true,
attributes: true,
subtree: true,
characterData: true,
attributeFilter: ['data-custom']
};
observer.observe(target, config);
注意:
- 必须同时指定childList、attributes和characterData中的一种或多种,若未均指定将报错。
- 对一个节点添加观察器,就像使用
addEventListener
方法一样,多次添加同一个观察器是无效的,回调函数依然只会触发一次。但是,如果指定不同的options
对象,就会被当作两个不同的观察器。
3.编写回调函数
回调函数会在DOM变动时被触发,它接受两个参数:mutationsList
和observer
。
mutationsList
:是一个Mutation Record对象的数组,每个对象描述一个DOM变动。observer
:对观察器本身的引用。
MutationRecord 对象包含了以下属性:
- type:一个字符串,表示变化的类型,可能是
"attributes"
(属性更改)、"childList"
(子元素的添加或移除)或"characterData"
(文本节点的更改)。- target:变化发生的目标元素。
- addedNodes:一个 NodeList 对象,包含了被添加的节点。仅当变化类型为
"childList"
时才存在。- removedNodes:一个 NodeList 对象,包含了被移除的节点。仅当变化类型为
"childList"
时才存在。- previousSibling:变化前目标节点的前一个同级节点。仅当变化类型为
"childList"
时才存在。- nextSibling:变化前目标节点的下一个同级节点。仅当变化类型为
"childList"
时才存在。- attributeName:发生属性更改时的属性名称。仅当变化类型为
"attributes"
时才存在。- oldValue:属性更改前的旧值。仅当变化类型为
"attributes"
时才存在。- attributeNamespace:变化属性的命名空间。仅当变化类型为
"attributes"
时才存在。
const callback = (mutationsList, observer) => {
mutationsList.forEach(mutation => {
if (mutation.type === 'childList') {
// 处理子元素的添加或删除
} else if (mutation.type === 'attributes') {
// 处理属性变化
}
});
};
4.disconnect()
方法
disconnect()
方法:用于停止 MutationObserver 实例的观察。一旦调用了disconnect()
方法,观察器将不再监听 DOM 变化,直到再次调用observe()
方法重新启动。
示例:
// 创建 MutationObserver 实例
const observer = new MutationObserver(callback);
// 开始观察目标元素
observer.observe(target, config);
// 停止观察
observer.disconnect();
5. takeRecords()
方法
takeRecords()
方法:用于获取当前尚未处理的 DOM 变化记录。通常,MutationObserver 的回调函数会在触发后处理 DOM 变化记录,但在某些情况下,我们可能需要在触发回调之前检查未处理的变化记录。
示例:
// 创建 MutationObserver 实例
const observer = new MutationObserver(callback);
// 开始观察目标元素
observer.observe(target, config);
// 获取未处理的 DOM 变化记录
const unprocessedRecords = observer.takeRecords();
// 手动处理未处理的记录
if (unprocessedRecords.length > 0) {
unprocessedRecords.forEach(record => {
// 处理未处理的记录
});
}
使用场景
- 懒加载:可以使用Mutation Observer来监测当元素进入视口时,动态加载内容,例如图片懒加载。
- 自动保存表单数据:可以在表单元素的值发生变化时触发保存操作,以避免数据丢失。
- 动态内容更新:在单页面应用(SPA)中,可以使用Mutation Observer来监测内容的
总结
MutationObserver固然好用,但是其也存在缺点。
-
首先是性能损耗 虽然在MutationEvent的基础上优化了许多,但是监听body的操作对性能影响还是非常大的,一切用户操作可能都会使函数频繁的回调。
解决方案:尽量限制监听范围,只监听必要的节点,或限制监听的变化类型,以减小回调的频率。
-
其次是操作冲突 由于回调函数非唯一性,如果两个观察者监听变化后的操作有依赖关系可能会造成错误或者冲突
解决方案:我们可以采用锁的机制,确保在满足特定条件时才能执行相关操作,以避免冲突和错误。
-
最后是无法在IFrame中监听变化MutationObserver操作是基于当前DOM进行监听的,所以无法跨线程与窗口
解决方案:使用跨线程和跨窗口通信机制,如`postMessage`,来实现监听和数据传递。