【JS 性能】前端性能优化基石:深入理解防抖(Debounce)与节流(Throttle)

【JS 性能】前端性能优化基石:深入理解防抖(Debounce)与节流(Throttle)

所属专栏: 《前端小技巧集合:让你的代码更优雅高效》
上一篇: 【JS 语法】代码整洁之道:解构赋值与展开语法的 5 个神仙用法
作者: 码力无边


引言:那一夜,我的页面因为一次滚动而卡死

嘿,各位热爱高性能代码的道友们,我是码力无边

在我们的前端江湖中,高性能是一个永恒的追求。我们不断优化渲染速度,减少网络请求,压缩资源包大小。但是,一个再完美的网站,也可能因为几个微小的操作,瞬间变得卡顿无比,让用户体验直线下降。

回想一下你可能遇到的场景:

  • 用户在输入框里快速输入搜索关键词,每输入一个字符,你的前端就要触发一次请求去后端搜索。结果就是:用户手速越快,页面越卡,甚至服务器都可能崩溃。
  • 用户在手机上疯狂滑动页面(scroll 事件),或者频繁拖拽一个元素(mousemove 事件)。这些事件在短时间内被触发了成百上千次,导致页面频繁重绘和回流,最终浏览器卡顿、崩溃。
  • 用户疯狂点击一个按钮(click 事件),导致重复提交表单,或者发送了多次请求。

这些问题,都是因为我们对高频事件的处理不当。浏览器不会怜悯你的 CPU,它会忠实地、毫秒不差地执行你绑定在事件监听器上的代码。

那么,如何驯服这些高频事件,在保证用户体验的前提下,减少代码的执行次数呢?答案就是我们今天的主角——防抖(Debounce)节流(Throttle)

它们就像是两个智能的“门卫”,负责管理进入你代码主体的事件流。一个负责“延迟放行”,一个负责“限量放行”。掌握它们,你就能让你的页面在处理高频事件时,依然如丝般顺滑,性能爆炸!

一、防抖(Debounce):“你停下来,我就执行”

1.1 核心思想:延迟执行,只执行最后一次

防抖的思路是:当事件连续触发时,我先不急着执行。我设定一个等待时间,如果在等待时间内事件又被触发了,我就重新开始计时。只有当事件停止触发,并且等待时间结束后,我才执行最后一次。

它就像坐地铁。地铁关门前,如果有人冲进来,门就会重新打开,等待下一个人冲进来。只有当地铁站安静下来一段时间,地铁才会真正关门开走。

最典型的应用场景:输入搜索框 (Input/Keyup)

用户输入通常是一连串的按键,如果我们每按一个键都去搜索,会浪费大量资源。我们希望用户输入完成后,停顿一下,再去搜索。

1.2 防抖的实现(基础版)

在 JavaScript 中,我们通常使用 setTimeout 来实现防抖。

/*** 防抖函数 (Debounce)* @param {Function} func - 需要执行的函数* @param {Number} delay - 延迟时间(毫秒)*/
function debounce(func, delay = 500) {let timeoutId = null; // 存储 setTimeout 的 ID,用于清除计时器return function(...args) {// 1. 在函数执行前,先清除上一次的计时器if (timeoutId) {clearTimeout(timeoutId);}// 2. 重新设置一个新的计时器timeoutId = setTimeout(() => {// 3. 延迟时间到了,执行我们传入的函数// 注意:使用 apply 或 call 来确保 func 内部的 this 和参数正确传递func.apply(this, args);timeoutId = null; // 执行完后可以重置 ID}, delay);};
}

如何使用:

function search(keyword) {console.log(`正在搜索:${keyword}`);// 假设这里是一个实际的后端请求
}// 应用防抖:延迟 300 毫秒
const debounceSearch = debounce(search, 300);// 绑定事件
const searchInput = document.getElementById('search-input');
searchInput.addEventListener('keyup', (event) => {debounceSearch(event.target.value);
});

当你快速敲击键盘时,search() 函数并不会立即执行,只有当你停顿超过 300 毫秒后,它才会执行一次。完美解决了高频触发搜索请求的问题。

二、节流(Throttle):“有节奏地执行”

2.1 核心思想:固定周期执行

节流的思路是:在单位时间内(比如 500 毫秒),不管事件触发了多少次,我只执行一次。

它就像游戏中的技能冷却。你按下技能键后,技能进入冷却时间,在这个冷却时间内,你无论怎么按,技能都无法再次释放,直到冷却时间结束。

最典型的应用场景:scroll, resize, mousemove

这些事件通常需要高频地获取位置信息或更新布局。但我们不需要每毫秒都执行一次,比如,我们只需要每 200 毫秒执行一次就足够了。

2.2 节流的实现(基础版)

节流通常使用“时间戳”或“定时器”来实现。这里我们用“时间戳”方式,它更加简单直接。

/*** 节流函数 (Throttle)* @param {Function} func - 需要执行的函数* @param {Number} interval - 时间间隔(毫秒)*/
function throttle(func, interval = 500) {let lastTime = 0; // 上次执行的时间戳return function(...args) {const now = Date.now(); // 当前时间戳// 1. 判断时间间隔是否达到if (now - lastTime > interval) {// 2. 执行函数,并更新上次执行时间lastTime = now;func.apply(this, args);}// 3. 如果时间间隔不够,不做任何事情};
}

如何使用:

function handleScroll() {console.log('滚动事件触发,正在处理...');// 假设这里是复杂的 DOM 计算或布局更新
}// 应用节流:每 200 毫秒最多执行一次
const throttledScroll = throttle(handleScroll, 200);// 绑定事件
window.addEventListener('scroll', throttledScroll);

无论用户滚动速度有多快,handleScroll() 函数都只会在每 200 毫秒的固定频率下执行,极大地减轻了浏览器的计算压力。

三、防抖 vs 节流:我该用哪个?

特性防抖 (Debounce)节流 (Throttle)
执行频率连续触发时,只执行最后一次连续触发时,在指定时间间隔内最多执行一次
等待时间事件停止触发一段时间后才执行事件开始触发后,在固定周期内执行
侧重场景用户完成操作,再执行持续操作,需要高频但有限制地执行
适用场景搜索输入、窗口调整大小(Resize)页面滚动(Scroll)、鼠标移动(Mousemove)、高频点击

总结:

  • 防抖(Debounce) 强调的是“只在连续操作结束后,执行一次最终的逻辑”。
  • 节流(Throttle) 强调的是“持续操作中,以固定频率进行执行”。

四、性能考量与注意事项

  1. 清除计时器: 如果使用 debounce,并且你的组件在计时器结束前被销毁了(比如 Vue/React 组件的卸载),你需要在卸载生命周期里调用 clearTimeout(timeoutId),防止内存泄漏。
  2. event 对象问题: 在使用 debouncethrottle 封装的函数中,如果你需要访问原始的 event 对象,要小心。因为在计时器执行时,event 对象可能已经被回收或重用了。通常的做法是,在外部函数中获取并传递你需要的 event 属性(如 event.target.value),或者在内部函数的参数列表中获取。在上面的例子中,我们已经使用了 ...argsapply() 来确保参数的传递正确性。
  3. 库的选择: 如果你的项目足够大,你可以直接使用成熟的工具库,如 Lodash 的 _.debounce_.throttle。它们的功能更完善,包含了我们今天没有涉及的“立即执行”等高级选项。但作为前端工程师,掌握其底层原理,在需要时能够手写出来,是必须的修炼。

写在最后:性能优化,从小处着手

防抖和节流,是前端性能优化中最基础,但也最有效的“心法”之一。它们能让你在处理高频事件时,将 CPU 和内存的压力降到最低,实现丝般顺滑的用户体验。

掌握它们,你不仅是解决了眼前的问题,更是养成了一种性能优先的编码习惯。当你下次再看到一个 scrollmousemove 事件时,你的大脑里就会自动响起警钟:“嘿,是时候请‘门卫’登场了!”


专栏预告与互动:

我们已经掌握了 JS 基础和性能优化的要诀。但一个大型项目,除了代码本身的逻辑,还需要考虑模块化、依赖管理等工程化问题。

下一篇,我们将探讨 JS 模块化的进阶用法——动态 import() 与代码分割的艺术。我们将学习如何在需要时才加载代码,有效缩减首屏加载时间,提升用户体验!

码力无边的修炼之旅,需要你的持续关注!如果你觉得今天的内容让你功力大增,请点赞、收藏、关注,助我继续“飞升”!

今日挑战: 防抖函数的 timeoutId 变量通常定义在外部作用域。如果你的代码被打包在模块中,并且有多个地方调用了 debounce 函数,会有问题吗?在评论区分享你的分析吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/95612.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/95612.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

线性代数 · 直观理解矩阵 | 空间变换 / 特征值 / 特征向量

注:本文为 “线性代数 直观理解矩阵” 相关合辑。 英文引文,机翻未校。 如有内容异常,请看原文。 Understanding matrices intuitively, part 1 直观理解矩阵(第一部分) 333 March 201120112011 William Gould Intr…

设计模式基础概念(行为模式):策略模式

概述 策略模式是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。 主要目的是通过定义相似的算法,替换if else 语句写法,并且可以随时相互替换 结构示例 策略…

功能组和功能组状态的概念关系和区别

在 AUTOSAR Adaptive Platform 中,功能组(Function Group,FG) 和 功能组状态(Function Group State) 是状态管理(SM)的核心概念,二者构成静态逻辑单元与动态行为模式的协…

力扣326:3的幂

力扣326:3的幂题目思路代码题目 给定一个整数,写一个函数来判断它是否是 3 的幂次方。如果是,返回 true ;否则,返回 false 。 整数 n 是 3 的幂次方需满足:存在整数 x 使得 n 3^x 思路 想要是三的幂次方的话将这个…

前瞻性技术驱动,枫清科技助力制造企业借助大模型完成生产力转化

麦肯锡于近期发布的《技术趋势展望2025》更清晰地定义了AI的角色与发展方向。报告在不止一个章节总结了基础模型加速小型化的趋势,多模态融合成为主流:企业的模型利用从追求“大而全”转向“小而精”,高效专用小模型成本降低90%的同时保持性能…

如何远程连接云服务器上mysql

一:使用系统命令查看端口占用# 查看MySQL进程及其端口sudo netstat -tlnp | grep mysql# 或者使用ss命令sudo ss -tlnp | grep mysql# 查看3306端口(MySQL默认端口)sudo netstat -tlnp | grep 3306出现如下信息,说明端口3306[root…

今日分享:C++模板(全详解)

😎【博客主页:你最爱的小傻瓜】😎 🤔【本文内容:C模板 😍 】🤔 -------------------------------------------------------------------------------------------------------------------…

ramdisk内存虚拟盘(一)——前世今生

1990 年代:前因——“硬盘太慢、驱动太多” 背景:早期 Linux 根文件系统要么在软盘、要么在 IDE 硬盘,内核把对应的软盘/IDE 驱动编进去即可顺利挂载。矛盾出现:随着 SCSI、PCMCIA、USB、RAID 控制器等百花齐放,如果把…

ETH持续上涨推动DEX热潮,交易活跃度飙升的XBIT表现强势出圈

BOSS Wallet 8月15日讯,随着ETH价格在过去24小时内强势拉升至4300美元,整个加密市场再度掀起涨势狂潮,链上交易活跃度空前高涨。其中,去中心化交易所平台迅速成为市场焦点,其平台活跃度与交易量双双上涨,吸…

Stand-In - 轻量级人物一致性视频生成 高保真视频人脸交换 ComfyUI工作流 支持50系显卡 一键整合包下载

Stand-In 是一个轻量级、即插即用的身份保护视频生成框架,只需要上传一张人物照片,加上一段提示词,即可生成高度一致性的高保真人物视频,人脸相似度和自然都几乎达到100%还原水平。 Stand-In 能把任何一张人脸(甚至动漫…

vue3相关基础

1、ref和reactive的区别两者都是响应式数据的声明。Reactive只适用于非基本数据类型&#xff0c;如对象&#xff0c;数组等。而ref是兼容适用于reactive的的数据类型的以及其他数据&#xff0c;灵活性较高。ref声明的变量取值时需要.value。在<template></template>…

云手机存储和本地存储的区别

云手机存储通常指云存储&#xff0c;即数据存储在云端服务器&#xff0c;本地存储则是将数据存储在用户设备硬件中&#xff0c;主要区别体现在存储位置、访问方式、依赖条件等多个方面&#xff0c;具体如下&#xff1a;本地存储主要是将数据存储在用户自有设备的物理硬件中&…

【科研绘图系列】R语言绘制三维曲线图

文章目录 介绍 加载R包 数据下载 导入数据 数据预处理 画图 系统信息 参考 介绍 【科研绘图系列】R语言绘制三维曲线图 加载R包 library(tidyverse) library(ggsignif) library(RColorBrewer) library(dplyr) library(reshape2) library(grid

python常用包

以下按类别列举10个常用Python包&#xff0c;并以一句话概括其核心作用&#xff1a; 一、数据分析与科学计算 NumPy&#xff1a;提供高性能多维数组及数学运算&#xff0c;是数值计算的基础库。Pandas&#xff1a;通过DataFrame结构实现高效表格数据清洗、分析与处理。SciPy&am…

“ 船新版本 ”

在 GeeLark 最新版本中&#xff0c;增强了 AIGC 生成能力以及 AI 协助自定义任务开发功能&#xff0c;给用户优化构建从内容生产到运营自动化的完整技术链&#xff0c;为跨境电商及企业用户提供更完善的智能化解决方案&#xff0c;效率翻倍轻松出海。 AIGC 接入 MiniMax-Hailuo…

力扣 —— 二分查找

搜索插入位置 35. 搜索插入位置 - 力扣&#xff08;LeetCode&#xff09; 算法思想&#xff1a; class Solution(object):def searchInsert(self, nums, target):left0 rightlen(nums)-1while left < right :mid (left right) // 2if nums[mid] < target:left mid 1…

USB ADB 简介

概念 ADB 是 Android 平台的 调试桥接协议&#xff0c;允许主机&#xff08;PC&#xff09;与 Android 设备通信。 通过 ADB&#xff0c;开发者可以执行命令、调试应用、传输文件、访问 shell、调试 logcat 等。 ADB 运行在 USB 或 TCP/IP 上&#xff0c;但最常用的是 USB 连…

【Golang】:数据类型

目录 1. 基本数据类型 1.1 布尔类型 1.2 整数类型 1.3 浮点数类型 1.4 复数类型 1.5 字符类型 1.6 字符串类型 2. 类型转换 2.1 基本数据类型 → string 2.2 string → 基本数据类型 3. 常量 1. 基本数据类型 1.1 布尔类型 Go中的布尔类型取值为true或false&#…

旋钮键盘项目---foc讲解(开环)

这里就不过多的讲解什么原理&#xff0c;公式的变换了&#xff0c;感兴趣的可以看灯哥开源&#xff0c;讲解的非常好的。当然&#xff0c;更细致的讲解&#xff0c;也可以看b站其他教学。 我这里主要讲解我对于开环部分的理解&#xff0c;以及stm32代码的实现逻辑。可以看作是…

数据科学与计算:爬虫和数据分析案例笔记

案例 1&#xff1a;中国大学排名爬取与分析 一、任务描述 目标&#xff1a;爬取高三网中国大学排名一览表&#xff0c;提取学校名称、总分、全国排名、星级排名、办学层级等数据&#xff0c;并保存为 CSV 文件。 网址&#xff1a;2021中国的大学排名一览表_高三网 二、任务…