前端性能优化篇:防抖和节流
参考: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~...

Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...

无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...

Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...

数据结构第5章:树和二叉树完全指南(自整理详细图文笔记)
名人说:莫道桑榆晚,为霞尚满天。——刘禹锡(刘梦得,诗豪) 原创笔记:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 上一篇:《数据结构第4章 数组和广义表》…...