当前位置: 首页 > news >正文

性能优化之懒加载 - 基于观察者模式和单例模式的实现

一、引入

        在前端性能优化中,关于图片/视频等内容的懒加载一直都是优化利器。当用户看到对应的视图模块时,才去请求加载对应的图像。 原理也很简单,通过浏览器提供的 IntersectionObserver - Web API 接口参考 | MDN (mozilla.org),观察“哪个元素和视口交叉”,从而进行懒加载。

        这个API具有很好的性能,因为它的监听是异步的,不会影响JS的主线程,所以比传统的“监听页面滚动”更佳。关于API的使用,这里就不做过多说明了,主要操作如下:

const DOM = document.querySelector('img')
const io = new IntersectionObserver((entries) => {entries.forEach((k) => {//回调函数,可以利用 k.target 是否和我们要监听的DOM元素相等,来判断当前是否是我们要监听的目标元素if(k.target === DOM){ /* 做懒加载的操作 */}});
}, {/*一些配置,详见MDN文档*/});
io.observe(DOM) //添加监听

 二、可优化的点

        值得注意的是,一个observer实例,可以监听多个DOM元素。如果我们需要封装一个图片组件,并实现它的懒加载,那么“每个组件都创建一个IntersectionObserver实例” 显然是不划算的,如果页面上有上百个图片,就会创建出上百个实例。

        针对这种情况,并且不想破坏组件的封装性,于是考虑把实例提升到全局,封装一个hook,从而每个组件都能自行添加入该实例的观察对象中。但是,监听的回调函数是创建实例的时候就决定的,后续添加进入的DOM元素,在回调函数中无法判断“是否轮到自己”了

三、观察者模式

        有什么办法能够让DOM元素动态的进入回调函数呢? 我们可以利用对象引用地址不变的特性,动态的往对象里添加数据,这样在回调函数触发时,就能够取出正确的数据了

        这里我的灵感其实来源于Vue3的响应式原理, 收集依赖 --> 监听 --> 触发依赖。(Vue3是多对多的发布-订阅模式, 这里是 一对多的观察者模式

/**回调函数的类型*/
type ObserverCallback = (entryData: IntersectionObserverEntry) => void
/** 键是DOM元素,值是该元素的回调函数Set (考虑到可能一个元素会有多个回调) */ 
const watchMap = new WeakMap<Element, Set<ObserverCallback>>()
const io = new IntersectionObserver((entries) => {entries.forEach((k) => {const set = watchMap.get(k.target)if(set){set.forEach((fn) => fn(k)) //从weakMap中取出对应的监听事件触发} });
}, {/*一些配置,详见MDN文档*/}); 

        剩下要做的就是“依赖收集”了。基于面向对象的思想 (可以创建多个实例,多处复用,互不干扰)。

        当有DOM元素需要被监听时,添加进weakMap中;需要取消监听时,移除; observer触发回调时,取出对应的元素的依赖,执行回调函数

        手写过观察者模式或者发布订阅模式的小伙伴,应该对下面的代码构造很熟悉。

/**视口监听器 - 观察者模式 */
export class ViewportObserverWatcher {/**IntersectionObserver 实例 */io: IntersectionObserver/**当前正在监听的元素的weakMap */watchMap = new WeakMap<Element, Set<ObserverCallback>>()constructor(options?: IntersectionObserverInit) {this.io = new IntersectionObserver((entries) => {entries.forEach((k) => {this.watchMap.get(k.target)?.forEach((fn) => fn(k)) //从weakMap中取出对应的监听事件触发});}, options);}/**添加对元素的一个监听回调,可以选择触发条件* @param target 目标元素* @param callback 回调函数* @param condition 触发回调条件 `true | false | undefined` 分别对应 `与视口边界交叉 | 不与视口交叉 | 都`*/addWatch = (target: Element, callback: ObserverCallback, condition?: boolean) => {const _callback: ObserverCallback = (k) => {if (condition == undefined) { }//无论如何都触发 else if ((condition !== k.isIntersecting)) return //当触发条件和实际情况不相同时,不触发 callback(k)}if (this.watchMap.has(target)) {this.watchMap.get(target)!.add(_callback)} else {this.io.observe(target)this.watchMap.set(target, new Set([_callback]))}}/**取消对元素的某个回调 */removeWatch = (target: Element, callback: ObserverCallback) => {const set = this.watchMap.get(target)if (set) {set.delete(callback)if (set.size === 0) {this.watchMap.delete(target)this.io.unobserve(target)}}}/**取消对该元素的全部回调 */cancelWatch = (target: Element) => {this.watchMap.delete(target)this.io.unobserve(target)}
}

四、写个Hook吧

1. 元素创建时,加入io的监听;

2. 触发懒加载之后,取消对该元素的监听。

3. 依赖项变化后,重复前面的逻辑。
4. 只要是元素,都能进行监听,不只是图片/视频。有需要使用到该功能的元素都能使用。

import { DependencyList, RefObject, useEffect, useRef } from "react";/**视口监听器 - 单例模式 */
const viewportObserver = new ViewportObserverWatcher() //注:如果你是NextJs, 在NextJS build的时候,不能直接实例化IntersectionObserver,否则会报错 (因为在走服务端代码) 可以先设置为null,后续给这个变量赋值/**懒加载Hook。懒加载触发后,将会取消监听* @param watchRef 要监听的DOM元素* @param onEntering 元素进入视口的回调函数* @param onDestroy useEffect的return中要做的事* @param deps useEffect的依赖数组 (当什么变化时,需要重新开始懒加载流程)*/
const useLazyLoad = (watchRef: RefObject<HTMLElement>, onEntering: ObserverCallback, onDestroy?: () => void, deps: DependencyList = []) => {/**是否完成懒加载 */const isLazySuccess = useRef(false);useEffect(() => {if (!watchRef.current) return; const callback: ObserverCallback = (k) => {//因为只要和视口在交叉,就会不断触发这个函数,故需要使用一个标识符来限制 if (isLazySuccess.current === false) {onEntering(k)isLazySuccess.current = true;viewportObserver!.removeWatch(watchRef.current!, callback) //加载完成就取消监听onEntering(k)}}viewportObserver.addWatch(watchRef.current, callback, true)return () => {if (watchRef.current && viewportObserver) viewportObserver.removeWatch(watchRef.current, callback); //卸载时也要取消监听 isLazySuccess.current = false;onDestroy && onDestroy()};}, deps)
}

使用方法: 核心思想:到了视口才赋值真实路径,其它时候使用占位符

/**视频组件 */
export default function Video({ src, className, otherProps }: VideoProps) {const outRef = useRef<HTMLDivElement>(null); //被监听的元素const [realSrc, setRealSrc] = useState<string>(); //存放展示的src,如果还没到视口就不展示useLazyLoad(outRef, () => setRealSrc(src));return (<div className={cn(className, "rounded")} ref={outRef}>{/* 其它逻辑.... */}{/* 正常展示视频 */}{realSrc && <video src={realSrc} {...otherProps} />}{/* 其它逻辑.... */}</div>);
}

五、使用效果

        结合前面文章写的的瀑布流组件,实现以下效果:

        (图片链接来源于 岁月小筑随机图片API接口-随机背景图片-随机图片API (xjh.me))

相关文章:

性能优化之懒加载 - 基于观察者模式和单例模式的实现

一、引入 在前端性能优化中&#xff0c;关于图片/视频等内容的懒加载一直都是优化利器。当用户看到对应的视图模块时&#xff0c;才去请求加载对应的图像。 原理也很简单&#xff0c;通过浏览器提供的 IntersectionObserver - Web API 接口参考 | MDN (mozilla.org)&#xff0c…...

【LeetCode刷题-链表】--1290.二进制链表转整数

1290.二进制链表转整数 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val; this.next next; }*…...

搭建Radius认证服务器 安当加密

搭建Radius认证服务器需要完成以下步骤&#xff1a; 安装Radius服务器软件。可以选择使用FreeRadius、Radiusd或WinRadius等开源软件&#xff0c;也可以选择使用商业软件如Cisco或安当ASP等。配置认证数据库。在Radius服务器上配置认证数据库&#xff0c;用于存储用户信息和认…...

Windows11恢复组策略编辑器功能的方法

原因分析 日常工作学习中,对 Windows 计算机上的问题进行故障排除时,有些高级用户经常使用组策略编辑器轻松修复它。通过其分层结构,您可以快速调整应用于用户或计算机的设置。如果搜索结果中缺少组策略编辑器,则可能必须使用注册表编辑器作为疑难解答工具,这是一种更复杂…...

Django实战项目-学习任务系统-查询列表分页显示

接着上期代码框架&#xff0c;6个主要功能基本实现&#xff0c;剩下的就是细节点的完善优化了。 接着优化查询列表分页显示功能&#xff0c;有很多菜单功能都有查询列表显示页面情况&#xff0c;如果数据量多&#xff0c;不分页显示的话&#xff0c;页面展示效果就不太好。 本…...

Git 拉取指定TAG/分支的代码

像其他版本控制系统&#xff08;VCS&#xff09;一样&#xff0c;Git 可以给仓库历史中的某一个提交打上标签&#xff0c;以示重要。 比较有代表性的是人们会使用这个功能来标记发布结点&#xff08; v1.0 、 v2.0 等等&#xff09;。 1.创建 tag 标签 $ git tag <tagname&…...

2-爬虫-代理池搭建、代理池使用(搭建django后端测试)、爬取某视频网站、爬取某视频网站、bs4介绍和遍历文档树

1 代理池搭建 2 代理池使用 2.1 搭建django后端测试 3 爬取某视频网站 4爬取某视频网站 5 bs4介绍和遍历文档树 1 代理池搭建 # ip代理-每个设备都会有自己的IP地址-电脑有ip地址---》访问一个网站---》访问太频繁---》封ip-收费&#xff1a;靠谱稳定--提供api-免费&#xff…...

动手学深度学习——残差网络ResNet(原理解释+代码详解)

残差网络ResNet 1. 函数类2. 残差块3. ResNet模型4. 训练模型 ResNet为了解决“新添加的层如何提升神经网络的性能”&#xff0c;它在2015年的ImageNet图像识别挑战赛夺魁 它深刻影响了后来的深度神经网络的设计&#xff0c;ResNet的被引用量更是达到了19万。 1. 函数类 假…...

MYSQL 8.0 配置CDC(binlog)

CDC&#xff08;Change Data Capture&#xff09;即数据变更抓取&#xff0c;通过源端数据源开启CDC&#xff0c;ROMA Connect 可实现数据源的实时数据同步以及物理表的物理删除同步。这里介绍通过开启Binlog模式CDC功能。 注意&#xff1a;1、使用MYSQL8.0及以上版本。 2、不…...

软件测试/测试开发丨ChatGPT能否成为PPT最佳伴侣

点此获取更多相关资料 简介 PPT 已经渗透到我们的日常工作中&#xff0c;无论是工作汇报、商务报告、学术演讲、培训材料都常常要求编写一个正式的 PPT&#xff0c;协助完成一次汇报或一次演讲。PPT相比于传统文本的就是有布局、图片、动画效果等&#xff0c;可以给到观众更好…...

java对象的创建过程

一.类的加载与检查 当我们new了一个对象的时候&#xff0c;首先会去检查一下这个指令是否在常量池中存在符号引用&#xff0c;并且检查这个符号引用代表的对象是否被加载&#xff0c;解析初始化过&#xff0c;如果没有就要先去进行类加载过程 二.分配内存 我们通过第一步的检…...

Salesforce创建一个页面,能够配置各种提示语,而不需要修改代码

在Salesforce中创建一个页面&#xff0c;并使其能够配置各种提示语&#xff0c;可以使用自定义设置、自定义对象或自定义标签等方法来实现。以下是一种常见的方法&#xff1a; 自定义对象或自定义设置&#xff1a;您可以创建一个自定义对象或自定义设置来存储各种提示语的信息。…...

轻松管理MySQL权限:Python脚本带你飞

数据库管理是 IT 专家和开发者日常工作中的重要组成部分。一个合适的用户权限管理系统不仅确保了数据的安全性,还能确保数据能够按照预期的方式被正确地访问和修改。在本文中,我们将探讨如何使用 Python 脚本来管理和查询 MySQL 数据库中的用户权限。 用户权限管理:创建或修…...

Py之transformers_stream_generator:transformers_stream_generator的简介、安装、使用方法之详细攻略

Py之transformers_stream_generator&#xff1a;transformers_stream_generator的简介、安装、使用方法之详细攻略 目录 transformers_stream_generator的简介 1、Web Demo T1、original T2、stream transformers_stream_generator的安装 transformers_stream_generator的…...

2023年Zotero最新同步教程-使用TeraCloud的25G免费空间实时跨设备同步文献

文章目录 1. 前言2.1. 注册账号2.1.1. 填写注册信息2.1.2. 创建账号成功2.1.3. 注意2.2. 扩容空间2.3. 打开WebDAV 3. Zotero配置WebDAV同步3.1. 设置网址3.2. 验证服务器3.3. 文件同步成功 4. 结语 1. 前言 Zotero免费版的存储空间是300m&#xff0c;一个图文PDF动辄两三M&am…...

面试题:用宏定义写出swap(x,y),即交换两数。

鼠标选中查看答案↓&#xff1a; #define swap(x,y) do{(x)(x)(y);(y)(x)-(y);(x)(x)-(y);}while(0) 这个题考查宏定义的语法&#xff0c;尤其是多行代码的宏定义&#xff0c;加上do{}while(),,可以保证这些语句只执行一次。...

微服务框架SpringcloudAlibaba+Nacos集成RabbitMQ

目前公司使用jeepluscloud版本&#xff0c;这个版本没有集成消息队列&#xff0c;这里记录一下&#xff0c;集成的过程&#xff1b;这个框架跟ruoyi的那个微服务版本结构一模一样&#xff0c;所以也可以快速上手。 1.项目结构图&#xff1a; 配置类的东西做成一个公共的模块 …...

低代码开发,一场深度的IT效率革命

目录 一、前言 二、低代码迅速流行的理由 三、稳定性和生产率的最佳实践 四、程序员用低代码开发应用有哪些益处&#xff1f; 1、提升开发价值 2、利于团队升级 五、总结 一、前言 尽管IT技术支撑了全球的信息化浪潮&#xff0c;然而困扰行业已久的软件开发效率并未像摩尔定律那…...

虚拟串口软件使用介绍

对于上位机开发来说(特别是串口通信应用),上机位软件的调试尤为重要,但是上机位软件的调试并不关心硬件,只需要关注验证发送的数据帧的接收情况,为了便于调试,可以将上机位软件与串口软件互通,实现数据的交互,但由于互通需要串口,可以借助串口虚拟软件(VSPD),虚拟出…...

如何编写一份完整的软件测试报告?(进阶版)百分之90不知道

背景 作为测试从业者&#xff0c;编写测试用例&#xff0c;测试计划&#xff0c;测试报告都是必经之路&#xff0c;最近完成了年终述职以及版本准出&#xff0c;感觉测试报告或者各类报告真是职场人不可或缺的一项技能&#xff0c;趁着热乎劲&#x1f525;&#xff0c;写下一些…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

python打卡day49

知识点回顾&#xff1a; 通道注意力模块复习空间注意力模块CBAM的定义 作业&#xff1a;尝试对今天的模型检查参数数目&#xff0c;并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建

制造业采购供应链管理是企业运营的核心环节&#xff0c;供应链协同管理在供应链上下游企业之间建立紧密的合作关系&#xff0c;通过信息共享、资源整合、业务协同等方式&#xff0c;实现供应链的全面管理和优化&#xff0c;提高供应链的效率和透明度&#xff0c;降低供应链的成…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业

6月9日&#xff0c;国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解&#xff0c;“超级…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...