开源项目推荐
ant-simple-pro一款用
vite构建的中台解决方案,支持vue3,react,angular,typescript等。jol-player一款简洁,美观,功能强大的react播放器
前言
自从vue出来之后,发布模式,订阅模式,观察者模式好像被大多数人所熟悉,如下图👇
但是上面毕竟是一种设计模式,而且对于设计模式,面向对象编程不是很熟悉的话,有点不太好理解的,那么原生的JavaScript有提供这样的对象吗😶,肯定有啦javascript不在是以前那个弱不禁风的语言了。
MutationObserver(变更观察者)
MutationObserver接口提供了监视对DOM树所做更改的能力。它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。
1. 概念
可以监听一个DOM树上节点的变化,以及一些属性的变更,它会在触发指定 DOM 事件时,调用指定的回调函数。MutationObserver 对 DOM 的观察不会立即启动;而必须先调用 observe() 方法来确定,要监听哪一部分的 DOM 以及要响应哪些更改。
2. 语法
const observer = new MutationObserver(callback);
参数
-
callback一个回调函数,每当被指定的节点或子树以及配置项有Dom变动时会被调用。回调函数拥有两个参数:一个是描述所有被触发改动的
MutationRecord对象数组,另一个是调用该函数的MutationObserver对象。
方法
-
阻止
MutationObserver实例继续接收的通知,直到再次调用其observe()方法,该观察者对象包含的回调函数都不会再被调用。 -
配置
MutationObserver在DOM更改匹配给定选项时,通过其回调函数开始接收通知。 -
从MutationObserver的通知队列中删除所有待处理的通知,并将它们返回到
MutationRecord对象的新Array中。
3. 使用
本文章的demo案例采用typescript,react,antd来进行编写演示。
基础用法
import React, { memo, useEffect, useState } from 'react';
import { Button, Space } from 'antd';
const MutationObservers = memo(function MutationObservers() {
const [list, setList] = useState<string[]>([]);
useEffect(() => {
// 选择需要观察变动的节点
const targetNode = document.getElementById('mutation');
// 观察器的配置(需要观察什么变动)
const config = { attributes: true, childList: true, subtree: true };
// 当观察到变动时执行的回调函数
const callback: MutationCallback = function (mutationsList, observer) {
mutationsList.forEach((mutation) => {
switch (mutation.type) {
case 'childList':
if (mutation.addedNodes.length) {
console.log('有一个节点被添加');
}
if (mutation.removedNodes.length) {
console.log('有一个节点被移除');
}
break;
case 'attributes':
console.log('这个 ' + mutation.attributeName + ' 属性有被改变过');
break;
case 'characterData':
console.log('characterData节点变化');
break;
}
});
};
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);
// 以上述配置开始观察目标节点
observer.observe(targetNode!, config);
return () => {
// 之后,可停止观察
observer.disconnect();
};
}, []);
return (
<>
<Button type="primary" onClick={() => setList((pre) => [...pre, '节点'])}>
添加元素
</Button>
<div id="mutation">
{list.map((item, index) => (
<div key={index}>
<Space>
<span>
{item}
{index + 1}
</span>
<Button
danger
size="small"
onClick={() => setList(list.filter((item, k) => k !== index))}
>
删除
</Button>
</Space>
</div>
))}
</div>
</>
);
});
export default MutationObservers;
4. 应用场景
-
监听JS脚本创建DOM渲染是否完成
-
监听Dom节点的变化,属性等
-
异步动态创建节点,执行一些操作
IntersectionObserver(交集观察者)
1. 概念
IntersectionObserver会注册一个回调函数,每当被监视的元素进入或者退出另外一个元素时(或者 viewport ),或者两个元素的相交部分大小发生变化时,该回调方法会被触发执行。这样,我们网站的主线程不需要再为了监听元素相交而辛苦劳作,浏览器会自行优化元素相交管理。
注意
IntersectionObserver无法提供重叠的像素个数或者具体哪个像素重叠,他的更常见的使用方式是——当两个元素相交比例在 N% 左右时,触发回调,以执行某些逻辑。
2. 语法
const observer = new IntersectionObserver(callback[, options]);
参数
callback当元素可见比例超过指定阈值后,会调用一个回调函数,此回调函数接受两个参数:entries一个IntersectionObserverEntry对象的数组,每个被触发的阈值,都或多或少与指定阈值有偏差。observer被调用的IntersectionObserver实例。
options可选 一个可以用来配置observer实例的对象。如果options未指定,observer实例默认使用文档视口作为root,并且没有margin,阈值为0%(意味着即使一像素的改变都会触发回调函数)。你可以指定以下配置:-
root监听元素的祖先元素Element对象,其边界盒将被视作视口。目标在根的可见区域的的任何不可见部分都会被视为不可见。 -
rootMargin一个在计算交叉值时添加至根的边界盒(bounding_box (en-US))中的一组偏移量,类型为字符串(string) ,可以有效的缩小或扩大根的判定范围从而满足计算需要。语法大致和CSS 中的margin属性等同; 可以参考 The root element and root margin in Intersection Observer API来深入了解margin的工作原理及其语法。默认值是"0px 0px 0px 0px"。 -
threshold规定了一个监听目标与边界盒交叉区域的比例值,可以是一个具体的数值或是一组0.0到1.0之间的数组。若指定值为0.0,则意味着监听元素即使与根有1像素交叉,此元素也会被视为可见. 若指定值为1.0,则意味着整个元素都在可见范围内时才算可见。可以参考Thresholds in Intersection Observer API 来深入了解阈值是如何使用的。阈值的默认值为0.0。
-
方法
-
终止对所有目标元素可见性变化的观察。
-
向
IntersectionObserver对象监听的目标集合添加一个元素。一个监听者有一组阈值和一个根, 但是可以监视多个目标元素,以查看这些目标元素可见区域的变化 -
返回一个
IntersectionObserverEntry对象数组, 每个对象的目标元素都包含每次相交的信息, 可以显式通过调用此方法或隐式地通过观察者的回调自动调用. -
停止对一个元素的观察
3. 使用
基础用法
import React, { memo, useEffect } from 'react';
const IntersectionComponent = memo(function IntersectionComponent() {
useEffect(() => {
const callback: IntersectionObserverCallback = (entries) => {
entries.forEach((item) => {
if (item.isIntersecting) {
console.log(`可见`);
} else {
console.log(`不可见`);
}
});
console.log(`entries`, entries);
};
const intersection = new IntersectionObserver(callback);
document.querySelector('#scrollerFooter') &&
intersection.observe(document.querySelector('#scrollerFooter')!);
return () => {
intersection.disconnect();
};
}, []);
return (
<div>
<p>ant-simple-pro</p>
{/* 省略了<p>ant-simple-pro</p>*/}
<div id="scrollerFooter">我要出来啦</div>
</div>
);
});
export default IntersectionComponent;
写个导航栏定位功能
import React, { memo, useEffect, useState } from 'react';
const Anchor = memo(function Anchor() {
const [dataKey, setDataKey] = useState<string | null>(null);
const navList = [
{ title: '家电区', bg: '#ff000026', id: 'homeAppliances' },
{ title: '服装区', bg: '#9c27b03d', id: 'clothing' },
{ title: '美食区', bg: '#3f51b547', id: 'delicacy' },
{ title: '生活区', bg: '#03a9f442', id: 'life' },
{ title: '婴儿区', bg: '#ff572238', id: 'baby2' },
];
const getStyle = (bg: string): React.CSSProperties => ({
background: bg,
height: '700px',
width: '70%',
margin: '20px 0',
});
const positioningNavigation = (id: string) => {
const element = document.querySelector(`#${id}`);
element && element.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
const callback: IntersectionObserverCallback = (entries) => {
entries.forEach((item) => {
if (item.isIntersecting) {
setDataKey(item.target.dataset.key);
}
});
};
const intersection = new IntersectionObserver(callback, { threshold: 0.8 });
navList.forEach((item) => {
const element = document.querySelector(`#${item.id}`);
element && intersection.observe(element);
});
return () => {
intersection.disconnect();
};
}, []);
return (
<div>
{navList.map((item, index) => (
<div style={getStyle(item.bg)} id={item.id} key={index} data-key={item.id}>
<h2 style={{ textAlign: 'center' }}>{item.title}</h2>
</div>
))}
<ul
style={{
position: 'fixed',
top: '20%',
right: '50px',
border: '1px solid #ccc',
textAlign: 'center',
}}
>
{navList.map((item) => (
<li
key={item.id}
style={{
padding: '10px',
cursor: 'pointer',
background: dataKey === item.id ? 'red' : 'none',
}}
onClick={() => positioningNavigation(item.id)}
>
{item.title}
</li>
))}
</ul>
</div>
);
});
export default Anchor;
4. 应用场景
- 图片懒加载
- 内容无限滚动
- 定位导航栏(如上案例)
- 在用户看见某个区域时执行任务或播放动画,视频等操作
ResizeObserver(视图观察者)
1. 概念
ResizeObserver提供了一种高性能的机制,通过该机制,代码可以监视元素的大小更改,并且每次大小更改时都会向观察者传递通知,接口可以监听到 Element 的内容区域或 SVGElement的边界框改变。内容区域则需要减去内边距padding。(有关内容区域、内边距资料见盒子模型 )。
2. 语法
const ResizeObserver = new ResizeObserver(callback)
参数
callback当尺寸发生变化时触发回调,使用ResizeObserverEntry对象数组调用该方法。
方法
-
取消和结束目标对象上所有对
Element或SVGElement观察。 -
开始观察指定的
Element或SVGElement -
结束观察指定的
Element或SVGElement
3. 使用
import React, { memo, useEffect } from 'react';
const ResizeObserverComponent = memo(function ResizeObserverComponent() {
useEffect(() => {
const callback: ResizeObserverCallback = (entries) => {
entries.forEach((item) => {
if (item.contentRect) {
const { width } = item.contentRect;
item.target.style.fontSize = Math.ceil(width / 14) + 'px';
}
});
};
const resizeObserver = new ResizeObserver(callback);
document.querySelector('#resize')! &&
resizeObserver.observe(document.querySelector('#resize')!);
return () => {
resizeObserver.disconnect();
};
}, []);
return (
<>
<div id="resize" style={{ textAlign: 'center', lineHeight: '150px' }}>
ant-simple-pro
</div>
</>
);
});
export default ResizeObserverComponent;
如上案例,会当
div的width改变的时候,会重新修改fontSize的样式,very good 😏
4. 应用场景
-
配合媒介查询 /
window.matchMedia实现更加强大的响应式布局 -
可以针对单个元素的改变,而重新整体的布局改变
-
简单的布局,不在依赖响应式设计理念,
ResizeObserver就可以做到
PerformanceObserver(性能观察者)
1. 概念
PerformanceObserver 用于监测性能度量事件,在浏览器的性能时间轴记录下一个新的 performance entries 的时候将会被通知,给定的观察者 callback 生成一个新的 PerformanceObserver 对象.当通过 observe() 方法注册的 条目类型 的 性能条目事件 被记录下来时,调用该观察者回调 。
2. 语法
const observer = new PerformanceObserver(callback);
参数
-
callback观察的性能事件被记录时将调用
PerformanceObserverCallback回调. 调用回调时,其第一个参数是 性能观察条目列表 (en-US),第二个参数是观察者对象。
语法
-
用于阻止性能观察者接收任何 性能条目 事件。
-
用于观察传入的参数中指定的性能条目类型的集合。当记录一个指定类型的性能条目时,性能监测对象的回调函数将会被调用。
-
返回当前存储在性能观察器中的
性能条目列表,将其清空
3. 使用
import React, { memo, useEffect } from 'react';
import { Button } from 'antd';
const PerformanceObserverComponent = memo(function PerformanceObserverComponent() {
useEffect(() => {
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
console.log(
'名称: ' +
entry.name +
', 类型: ' +
entry.entryType +
', 开始时间: ' +
entry.startTime +
', 时间: ' +
entry.duration +
'\n',
);
});
});
observer.observe({ entryTypes: ['resource', 'mark', 'measure'] });
performance.mark('registered-observer');
return () => {
observer.disconnect();
};
}, []);
const trigger = () => {
performance.measure('button clicked');
};
return (
<>
<Button danger size="small" onClick={trigger}>
点击我看性能
</Button>
</>
);
});
export default PerformanceObserverComponent;
我们可以通过页面加载,和点击按钮,可以检测到页面的响应速度性能,very good 😏
4. 应用场景
- 可以用来检测页面性能,静态资源等
- 可以用来作为开发性能
SDK插件 - 可以用来做性能可视化
结论
MutationObserver,IntersectionObserver,ResizeObserver,PerformanceObserver这四个对象,大家可以尝试用到自已的项目中去,前提是不兼容低端游览器,像MutationObserver,ResizeObserver这两个对象,建议小伙伴们必须掌握和知道,因为用的较多,vue中就用到了MutationObserver。