在现代Web开发中,Observer(观察者)模式是实现事件驱动架构的一种重要方式。H5及相关的JavaScript API引入了多种Observer,使得开发者能够更加高效地监听DOM变化、Intersection状态等。下面,本文将介绍几种重要的Observer及其使用示例。
1. MutationObserver
MutationObserver 提供了一种监听DOM树变化的方法。无论是元素属性的更改、文本内容的变化还是子节点的添加和删除,都可以通过它来捕获。
示例代码:
// 选择需要观察的目标节点
var targetNode = document.getElementById('some-id');
// 配置观察选项:
var config = { attributes: true, childList: true, subtree: true };
// 当观察到变动时的回调函数
var callback = function(mutationsList, observer) {
for(var mutation of mutationsList) {
if (mutation.type == 'childList') {
console.log('A child node has been added or removed.');
} else if (mutation.type == 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
};
// 创建一个观察器实例并传入回调函数
var observer = new MutationObserver(callback);
// 开始观察目标节点
observer.observe(targetNode, config);
// 停止观察
// observer.disconnect();
相关属性
- mutation.type:表示发生的变化类型。常见的类型包括
attributes(属性变更)、characterData(字符数据变更)、childList(子节点变更)。 - mutation.target:返回被添加或移除的节点,或者属性被更改的节点。
- mutation.addedNodes / mutation.removedNodes:分别返回被添加或移除的节点列表。
- mutation.attributeName / mutation.oldValue:当
mutation.type为attributes时,attributeName表示哪个属性发生了变化,而oldValue则提供该属性变化前的值。
相关方法
-
observe(target, options):开始观察指定的目标节点。其中
options参数是一个对象,可以包含如下选项:childList: 布尔值,设置是否监视直接子节点的变动。attributes: 布尔值,设置是否监视属性的变动。characterData: 布尔值,设置是否监视数据(Text节点)的变动。subtree: 布尔值,设置是否监视目标节点的所有后代节点的变动,默认为false。attributeOldValue: 如果为true,则在mutation对象中会包含oldValue属性来表示旧的属性值。characterDataOldValue: 类似于attributeOldValue,但是针对字符数据的旧值。attributeFilter: 数组,限制仅监听特定属性的变化。
-
disconnect():停止观察所有目标节点。调用此方法后,将不会再有新的突变记录被传递给回调函数,直到再次调用
observe()方法。 -
takeRecords():清空队列中的所有突变记录,并返回这些记录。这对于希望在断开连接之前获取所有未处理的突变信息非常有用。
在一些手写Promise的面试题里可能会用到这个MutationObserver来去模拟提供任务进入微队列的服务。
MutationObserver 不仅提供了监听DOM变化的强大功能,还利用了JavaScript运行时的特性——微任务队列(Microtask Queue),来确保所有DOM操作都已完成之后再触发观察者回调。这种机制是基于浏览器的事件循环模型,其中微任务队列优先于宏任务队列(如setTimeout、UI渲染等)执行。这意味着当DOM发生变化时,相应的MutationObserver回调不会立即执行,而是被安排在一个微任务中,在当前任务的所有同步代码执行完毕后,下一次事件循环开始前执行。这种方式保证了即使在连续的DOM变更情况下,MutationObserver也只会触发一次回调,极大地提升了性能和效率。
2. IntersectionObserver
IntersectionObserver 可以用来异步观察目标元素与祖先元素或顶级文档视口(viewport)交叉(即可见性)的情况。这对于实现懒加载、无限滚动等功能非常有用。
示例代码:
let observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if(entry.isIntersecting) {
console.log('元素进入视口');
// 加载图片或其他资源
} else {
console.log('元素离开视口');
}
});
});
let target = document.querySelector('#target-element');
observer.observe(target);
相关属性
在 IntersectionObserver 的回调函数中,IntersectionObserverEntry 对象提供了丰富的信息来描述目标元素的可见性变化:
- isIntersecting:布尔值,表示目标是否当前与视口相交(即是否可见)。这是一个方便的快捷方式,避免了手动比较
intersectionRatio是否大于0。 - intersectionRatio:一个介于0到1之间的数值,表示目标元素与视口重叠的比例。当这个值为0时,意味着元素完全不在视口中;当值为1时,表示元素完全位于视口内。
- boundingClientRect:提供目标元素的大小及其相对于视口的位置。
- rootBounds:如果根元素是视口,则返回视口的大小;如果指定了自定义根元素,则返回该元素的大小。
- intersectionRect:表示两个矩形(目标元素和根元素)的交集部分的大小和位置。
- target:被观察的目标元素。
相关方法
-
observe(target):开始观察指定的目标元素。你可以对多个元素调用此方法以同时观察它们。
-
unobserve(target):停止观察特定的目标元素。当你不再需要监听某个元素的变化时,可以使用此方法取消观察。
-
disconnect():停止观察所有目标元素。一旦调用了此方法,就不会再有新的交叉状态变更触发回调,直到再次调用
observe()方法。 -
观察选项(options):
root: 指定作为参照容器的元素,默认是浏览器视窗。如果要监控元素相对于另一个滚动容器的可见性,可以在这里指定那个容器。rootMargin: 类似于CSS中的margin,用来扩展或缩小根(root)元素的边界。默认值为 "0px 0px 0px 0px"。threshold: 可以是一个单一的数字或者一个数组,用来指定触发回调的交叉比例。例如,值为0.5表示当至少一半的目标元素出现在视口中时触发回调。默认值为0,意味着只要有任何部分进入视口就会触发回调。
IntersectionObserver 提供了一种高效且灵活的方式来处理元素的可见性变化,使得我们可以轻松实现诸如图片懒加载、广告曝光统计等复杂功能。此外,由于它是基于异步通知机制设计的,因此相比传统的事件监听方式更加节省资源。
3. ResizeObserver
ResizeObserver 用于监听元素尺寸变化。这在响应式设计中特别有用,当用户调整浏览器窗口大小或者动态改变某个元素的大小时,可以触发相应的回调处理逻辑。
示例代码:
const resizeObserver = new ResizeObserver(entries => {
for (let entry of entries) {
console.log('Element:', entry.target);
console.log('New size:', entry.contentRect.width, 'x', entry.contentRect.height);
}
});
// 观察一个指定的元素
resizeObserver.observe(document.querySelector('aside'));
相关属性
在 ResizeObserver 的回调函数中,ResizeObserverEntry 对象提供了有关被观察元素尺寸变化的信息:
- contentRect:提供了一个
DOMRectReadOnly对象,包含了目标元素的内容框(不包括边框、内边距或外边距)的相关信息,如宽度(width)、高度(height)、顶部(top)、左侧(left)等。这是最常用的部分,用来获取元素的实际内容区域尺寸。 - target:表示被观察的目标元素本身。
- 还有部分不太常用的属性,可以直接打印对象查看。
相关方法
-
observe(target):开始观察指定的目标元素。可以对多个元素调用此方法以同时观察它们。例如:
resizeObserver.observe(document.querySelector('aside')); -
unobserve(target):停止观察特定的目标元素。当你不再需要监听某个元素的变化时,可以使用此方法取消观察。这对于性能优化非常有用,因为它允许你根据应用的状态动态管理观察者。
-
disconnect():停止观察所有目标元素。一旦调用了此方法,就不会再有新的尺寸变更触发回调,直到再次调用
observe()方法。这在你确定不需要继续监听任何元素的尺寸变化时很有用,可以帮助释放资源。
此外,值得注意的是,ResizeObserver 不仅可以监听直接的尺寸变化,还可以通过监听嵌套的子元素来间接感知布局变化的影响。这意味着即使父容器的尺寸没有直接改变,如果它的子元素导致了重新布局,ResizeObserver 也能检测到这种变化。