前端性能优化篇:防抖和节流
参考:JS问题:项目中如何区分使用防抖或节流?
面试官:什么是防抖和节流?有什么区别?如何实现?
1 为什么要用到防抖和节流
- 当函数绑定一些持续触发的事件如:浏览器的resize、scroll、keypress、mousemove ,键盘输入,多次快速click等等。
- 如果每次触发都要执行一次函数,会带来性能下降,资源请求太频繁等问题。
- 为了优化体验,需要对这类事件进行调用次数的限制,对此我们就可以采用 防抖(debounce) 和 节流(throttle) 的方式来减少调用频率。
防抖(Debounce) 和 节流(Throttle) 是前端开发中最常用的优化处理方式,本质上是 优化高频率执行代码 的一种手段。
2 防抖和节流两者定义的区分
2.1 简单定义区分
-
防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时,即一段时间内,多次执行变为只执行
最后一次。 -
节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效,即一段时间内,多次执行变为只执行
第一次。
2.2 详细定义区分
- 防抖(debounce):是指在一段时间内多次触发相同事件时,只执行最后一次触发的事件。这意味着,在一系列触发事件中,如果在指定的时间间隔内,发生了新的触发事件,那么前面的触发事件将被忽略,只有最后一次触发事件会被执行。
- 节流(throttle):是指在一段时间内多次触发相同事件时,只执行一次事件。这意味着,无论触发事件发生多少次,在指定的时间间隔内只会执行第一次事件。
一个经典的比喻:
想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应。
假设电梯有两种运行策略 debounce 和 throttle,超时设定为15秒,不考虑容量限制。
电梯第一个人进来后,15秒后准时运送一次,这是节流。
电梯第一个人进来后,等待15秒。如果过程中又有人进来,15秒等待重新计时,直到15秒后开始运送,这是防抖。
3 防抖和节流两者应用场景的区分
真实的项目中,在不同的场景下灵活切换使用防抖或节流,才会更加减少不必要的资源消耗,更加提高前端应用的性能和响应性。
3.1 防抖(Debounce)的应用场景
- 搜索框输入: 当用户在搜索框中输入内容时,可以使用防抖来延迟触发搜索请求。只有在用户停止输入一段时间后才会发送请求,避免频繁的请求发送。
- 窗口调整resize: 当窗口大小调整时,可以使用防抖来优化执行某些操作的频率,例如重新计算布局或重新渲染页面。
- 按钮点击: 当用户点击一个按钮时,可以使用防抖来确保只有在用户停止点击一段时间后才会执行相应的操作,避免误操作或重复执行。
- 手机号、邮箱验证输入检测。
3.2 节流(Throttle)的应用场景
- 页面滚动: 当用户滚动页面时,可以使用节流来限制触发滚动事件的频率。例如,在滚动过程中只执行某些操作的固定时间间隔,以减少事件处理的次数。
- 鼠标移动: 当用户移动鼠标时,可以使用节流来控制触发鼠标移动事件的频率。例如,在一定时间内只执行一次鼠标移动的处理逻辑,避免过多的计算和渲染操作。
- 实时通信: 在实时通信应用中,如聊天应用或在线协作工具,可以使用节流来限制发送消息的频率,以避免发送过多的请求或数据。
- 高频点击提交,表单重复提交。
4 防抖
Vue2:
/*** @param {Function} func* @param {number} wait* @param {boolean} immediate* @return {*}*/
export function debounce(func, wait, immediate) {let timeout, args, context, timestamp, resultconst later = function() {// 据上一次触发时间间隔const last = +new Date() - timestamp// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 waitif (last < wait && last > 0) {timeout = setTimeout(later, wait - last)} else {timeout = null// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用if (!immediate) {result = func.apply(context, args)if (!timeout) context = args = null}}}return function(...args) { // 返回一个包装后的函数context = this // 保存函数执行上下文对象timestamp = +new Date()const callNow = immediate && !timeout // 是否立即调用函数的条件// 如果延时不存在,重新设定延时if (!timeout) timeout = setTimeout(later, wait)if (callNow) { // 如果满足立即调用条件,则立即执行函数result = func.apply(context, args)context = args = null}return result}
}
Vue3:
export const useDebounce = (func: Function, delay: number, immediate: boolean = false) => {// let timer: ReturnType<typeof setTimeout>;let timer: NodeJS.Timeout | null = null;return (...args: any[]) => {if(timer) clearTimeout(timer);timer = setTimeout(() => {func(...args);}, delay)}}
手写防抖函数:
// 手写防抖函数/*** 执行频率:在事件停止触发的延迟时间内执行一次* * 防抖函数应用场景:* 1、频繁的点击按钮,触发某个事件* 2、监听浏览器滚动事件,完成某些特定操作* 3、用户缩放浏览器的resize事件等等* * 目的:延迟执行,确保只在停止操作后执行一次* *//*** @param {function} func - 目标对象* @param {number} wait - 防抖间隔时间* @param {boolean} immediate - 开启初次立即执行* @param {function} resultCallback - 目标对象返回值的回调函数* @return {function} - 防抖函数* @author m* @example** ```js* const inputChange = function (event) {* console.log(`发送了第${++counter}次网络请求`, this, event)* // 返回值* return "test"* }* * const debounceChange = debounce(inputChange, 2000, false, (res) => {* console.log("拿到真正执行函数的返回值:", res)* })* * // 取消功能* const cancelBtn = document.querySelector("#cancel") // 获取取消按钮* cancelBtn.onclick = function() {* debounceChange.cancel()* }* ```* */function debounce(func, wait = 200, immediate = false, resultCallback) {// 保存每次定时器操作,在下一次规定时间内 重复调用提供取消信息let timer = null;// 作为判断当前为立即执行 or 防抖模式let isInvoke = false;const _debounce = function (...args) {// 排除第一次调用无timer,其余规定时刻清除上一次定时器if (timer) clearTimeout(timer)if (immediate && !isInvoke) {const result = func.apply(this, args);// 将目标对象返回值返回后,能够进行回调执行处理if (resultCallback) resultCallback(result);// 初次立即执行,后续正常防抖模式isInvoke = true;} else {// 定时,正常防抖模式内容timer = setTimeout(() => {const result = func.apply(this, args) // 绑定this为元素本身以及event事件// 将目标对象返回值返回后,能够进行回调执行处理if (resultCallback) resultCallback(result);// 防抖模式结束,下次输入内容立即执行isInvoke = false;}, wait)}}// 封装取消功能_debounce.cancel = function () {if (timer) clearTimeout(timer);// 初始化timer = null;isInvoke = false;}return _debounce;
}
5 节流
Vue2:
// 节流函数
export function throttle(func, wait) {let timeout; // 定义一个计时器变量,用于限制函数调用频率return function (...args) { // 返回一个包装后的函数const context = this; // 保存函数执行上下文对象if (!timeout) { // 如果计时器不存在func.apply(context, args); // 执行函数timeout = setTimeout(() => {timeout = null; // 清空计时器变量}, wait); // 创建计时器,在指定时间后重置计时器变量}};
手写节流函数:
// 手写节流函数/*** 执行频率:在事件频繁触发时,以固定的时间间隔执行* * 应用场景:页面滚动、鼠标移动、窗口调整大小等高频事件* * 目的:限制执行频率,降低函数调用的次数*//*** 创建一个节流函数,在给定的时间间隔内最多执行一次目标函数* @param {function} func - 需要节流的目标函数* @param {number} intervalTime - 节流的间隔时间(毫秒)* @param {Object} [options] - 节流的配置选项* @param {boolean} [options.leading = true] - 是否在第一次调用时立即执行* @param {boolean} [options.trailing = false] - 是否在冷却时间结束后追加一次执行* @param {function} [options.resultCallback] - 每次节流函数执行后的回调函数,用于处理目标函数的返回值* @return {function} - 返回包装后的节流函数 * @author m* @example** ```js* // 创建一个节流函数,每 1000 毫秒最多执行一次* const throttledFn = throttle(() => {* console.log('Throttled function executed');* }, 1000);* * // 绑定事件* document.addEventListener('scroll', throttledFn);* * // 调用 cancel 方法* throttledFn.cancel();* ```* */function throttle(func, intervalTime = 200, options = { leading: true, trailing: false, resultCallback }) {// 记录上一次的开始时间const { leading, trailing, resultCallback } = options;// 每次点击时,重置当前最新时间let lastTime = 0;// 记录let timer = null;const _throttle = function (...args) {// 获取"第一次"调用时间let nowTime = new Date().getTime();// 必须第一次执行(lastTime) 且 用户传参调节为首次不立即执行(leading)if (!lastTime && !leading) lastTime = nowTime;// 获取冷却时间 = 节流间隔时间 - 第一次响应时间 + 发起响应时间const remainTime = intervalTime - nowTime + lastTime;// 达到冷却,可以执行响应函数if (remainTime <= 0) {// 如果节流间隔后存在正常响应函数,将"最后一次响应函数"的定时器清除if (timer) {clearTimeout(timer);timer = null;}const result = func.apply(this, args);// 返回值if (resultCallback) resultCallback(result);// 记录函数上一次被成功调用的时间戳lastTime = nowTime;return;}// 节流最后一次也可以执行的判断逻辑if (trailing && !timer) {// 返回timer,只有冷却归零后还未存在正常调用,timer尚未清零才能调用,一旦调用则重置timertimer = setTimeout(() => {timer = setTimeout(() => {timer = null;lastTime = !leading ? 0 : new Date().getTime();const result = func.apply(this, args);// 返回值if (resultCallback) resultCallback(result);})}, remainTime);}}// 取消方法_throttle.cancel = function () {// 有值的情况才进行取消if (timer) clearTimeout(timer);// 初始化timer = null;lastTime = 0;}return _throttle;
}
参考:JS高级-手写防抖节流函数与实现事件总线
相关文章:
前端性能优化篇:防抖和节流
参考:JS问题:项目中如何区分使用防抖或节流? 面试官:什么是防抖和节流?有什么区别?如何实现? 1 为什么要用到防抖和节流 当函数绑定一些持续触发的事件如:浏览器的resize、scroll…...
同为科技(TOWE)柔性定制化PDU插座
随着科技的进步,越来越多的精密电子设备,成为工作生活密不可分的工具。 电子电气设备的用电环境也变得更为复杂,所以安全稳定的供电是电子电气设备的生命线。 插座插排作为电子电气设备最后十米范围内供配电最终核心部分,便捷、安…...
【云原生系列】云计算中的负载均衡是什么,有什么用
云计算里有一个非常重要的概念叫“负载均衡”,如果你经常听到这个词但还不太明白具体是怎么回事,这篇文章可以给你一些思路。负载均衡简单来说就是“分担压力”,确保访问量被合理地分配到各个服务器上,让系统高效且稳定地运行。 …...
工业—使用Flink处理Kafka中的数据_ChangeRecord2
使用 Flink 消费 Kafka 中 ChangeRecord 主题的数据,每隔 1 分钟输出最近 3 分钟的预警次数最多的 设备,将结果存入Redis 中, key 值为 “warning_last3min_everymin_out” , value 值为 “ 窗口结束时间,设备id” &am…...
【Java-数据结构篇】Java 中栈和队列:构建程序逻辑的关键数据结构基石
我的个人主页 我的专栏:Java-数据结构,希望能帮助到大家!!!点赞❤ 收藏❤ 一、引言 1. 栈与队列在编程中的角色定位 栈和队列作为两种基本的数据结构,在众多编程场景中都有着独特的地位。它们为数据的有序…...
工业—使用Flink处理Kafka中的数据_ProduceRecord1
1 、 使用 Flink 消费 Kafka 中 ProduceRecord 主题的数据,统计在已经检验的产品中,各设备每 5 分钟 生产产品总数,将结果存入Redis 中, key 值为 “totalproduce” , value 值为 “ 设备 id ,最近五分钟生…...
探索CSS版心布局:构建现代网页的黄金比例
探索CSS版心布局:构建现代网页的黄金比例 在网页设计中,版心(或称为内容区域)是页面的核心部分,通常用于放置主要内容。使用CSS3的新特性,可以创建更加灵活和响应式的版心布局。本文将详细介绍如何使用CSS…...
华为NPU服务器昇腾Ascend 910B2部署通义千问Qwen2.5——基于mindie镜像一路试错版(三)
文章目录 前言纯模型推理启动服务后面干什么?这可咋整啊?愁死了!总结前言 这是咱这个系列的第三个文章了。 毕竟,这是我好几天摸索出的经验,能帮助各位在几个小时内领会,我觉得也算是我的功劳一件了。 所以,一是希望大家耐心看下去,耐心操作下去;而是恳请各位多多关…...
详解Java数据库编程之JDBC
目录 首先创建一个Java项目 在Maven中央仓库下载mysql connector的jar包 针对MySQL版本5 针对MySQL版本8 下载之后,在IDEA中创建的项目中建立一个lib目录,然后把刚刚下载好的jar包拷贝进去,然后右键刚刚添加的jar包,点击‘添…...
基于MFC实现的人机对战五子棋游戏
基于MFC实现的人机对战五子棋游戏 1、引言 此报告将详细介绍本次课程设计的动机、设计思路及编写技术的详细过程,展现我所学过的C知识以及我通过本次课程设计所学到例如MFC等知识。在文档最后我也会记录我所编写过程遇到的问题以及解决方案。 1.1 背景 五子棋是…...
AIGC 时代的文学:变革与坚守
目录 一.AIGC 带来的文学变革 1.创作方式的改变 2.阅读体验的升级 3.文学市场的重塑 二.文学在 AIGC 时代的坚守 1.人类情感的表达 2.文学的艺术性 3.文学的社会责任 三.AIGC 与人类作家的共生之路 1.相互学习 2.合作创作 3.共同发展 另: 总结 随着人…...
InfluxDB 集成 Grafana
将InfluxDB集成到Grafana进行详细配置通常包括以下几个步骤:安装与配置InfluxDB、安装与配置Grafana、在Grafana中添加InfluxDB数据源以及创建和配置仪表板。以下是一个详细的配置指南: 一、安装与配置InfluxDB 下载与安装: 从InfluxDB的官…...
笔记本电脑usb接口没反应怎么办?原因及解决方法
笔记本电脑的USB接口是我们日常使用中非常频繁的一个功能,无论是数据传输、充电还是外接设备,都离不开它。然而,当USB接口突然没有反应时,这无疑会给我们的工作和学习带来不小的困扰。下面,我们就来探讨一下笔记本USB接…...
【开源】A060-基于Spring Boot的游戏交易系统的设计与实现
🙊作者简介:在校研究生,拥有计算机专业的研究生开发团队,分享技术代码帮助学生学习,独立完成自己的网站项目。 代码可以查看项目链接获取⬇️,记得注明来意哦~🌹 赠送计算机毕业设计600个选题ex…...
static关键字在嵌入式C编程中的应用
目录 一、控制变量的存储周期和可见性 1.1. 局部静态变量 1.2. 全局静态变量 二、控制函数的可见性 2.1. 静态函数 2.2. 代码示例(假设有两个文件:file1.c和file2.c) 三、应用场景 3.1. 存储常用数据 3.2. 实现内部辅助函数 四、注…...
集合框架(1)
集合框架(1) 1、数组的特点与弊端 (1)特点: 数组初始化以后,长度就确定了。数组中的添加的元素是依次紧密排列的,有序的,可以重复的。数组声明的类型,就决定了进行元素初…...
Java 基础之泛型:类型安全的保障与灵活运用
在 Java 编程的世界里,泛型是一个至关重要且非常实用的特性。它在 Java 5 中被引入,从根本上改变了我们处理数据类型的方式,提供了更强的类型安全保障,同时也增加了代码的复用性和可读性。 一、什么是泛型 泛型(Gener…...
开发者如何使用GCC提升开发效率Opencv操作
看此篇前请先阅读 https://blog.csdn.net/qq_20330595/article/details/144134160?spm=1001.2014.3001.5502 https://blog.csdn.net/qq_20330595/article/details/144134160?spm=1001.2014.3001.5502 https://blog.csdn.net/qq_20330595/article/details/144216351?spm=1001…...
矩阵加法
矩阵加法 C语言代码C 语言代码Java语言代码Python语言代码 💐The Begin💐点点关注,收藏不迷路💐 输入两个n行m列的矩阵A和B,输出它们的和AB。 输入 第一行包含两个整数n和m,表示矩阵的行数和列数。1 <…...
yarn : 无法加载文件 E:\node\node_global\yarn.ps1,因为在此系统上禁止运行脚本
先确保安装了yarn —— npm install -g yarn 终端输入set-ExecutionPolicy RemoteSigned 若要在本地计算机上运行您编写的未签名脚本和来自其他用户的签名脚本,请使用以下命令将计算机上的执行策略更改为RemoteSigned 再去使用yarn okk~...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
