防抖与节流:前端性能优化的两大利器

在现代 Web 开发中,用户交互越来越频繁,而每一次交互都可能触发复杂的逻辑处理或网络请求。如果不加以控制,这些高频操作会带来严重的性能问题。为此,防抖(Debounce)节流(Throttle) 成为了前端开发中不可或缺的性能优化手段。

本文将结合一段实际代码和详细注释,深入浅出地讲解防抖与节流的核心思想、实现方式以及适用场景,并重点解析其中的关键逻辑。


一、为什么需要防抖和节流?

设想这样一个场景:用户在搜索框中输入关键词,每按一次键就发起一次 AJAX 请求获取搜索建议。如果用户快速输入"react",那么会依次触发 rrereareacreact 五次请求。

  • 问题1:请求开销大
    每次请求都需要消耗带宽、服务器资源,甚至可能造成接口限流。
  • 问题2:用户体验差
    如果请求响应慢,旧的请求结果可能会覆盖新的输入内容,导致显示错乱。

因此,我们需要一种机制来减少不必要的执行次数,只保留关键的操作。这就是防抖和节流要解决的问题。

防抖 :在一定时间内,只执行最后一次操作。
节流:每隔固定时间,最多执行一次操作。


二、防抖(Debounce)------"只认最后一次"

1. 核心思想

无论执行多少次,只执行最后一次。

就像王者荣耀中的"回城"技能:如果你在回城过程中被攻击,回城会被打断并重新计时。只有当你完整地等待一段时间后,回城才会真正生效。

2. 代码实现与闭包应用

javascript 复制代码
// 高阶函数 参数或者返回值是函数 (返回值是函数 -> 闭包)
function debounce(fn, delay) {
  var id; // 自由变量,闭包保存
  return function(args) {
    if (id) clearTimeout(id); // 清除已有定时器,重新计时
    var that = this; // 保存 this 上下文
    id = setTimeout(function() {
      fn.call(that, args); // 延迟执行原函数 并绑定正确的this和参数
    }, delay);
    // 这样只有最后一次触发后等待delay毫秒后才会真正执行
  };
}

关键点解析:

  • 闭包的作用id 是一个自由变量,被返回的函数所引用,从而在多次调用之间保持状态。这使得每次触发都能访问并清除上一次的定时器。
  • clearTimeout(id) :确保只有最后一次触发后的 delay 时间才会真正执行函数。
  • this 和参数传递 :通过 callapply 确保原函数在正确的上下文中执行,并传入正确的参数。

3. 使用示例

ini 复制代码
const inputb = document.getElementById('debounce');
let debounceAjax = debounce(ajax, 200);
inputb.addEventListener('keyup', function(e) {
  debounceAjax(e.target.value);
});

用户快速输入时,只有停止输入 200ms 后,才会发送最终的完整关键词请求,极大减少了无效请求。


三、节流(Throttle)------"冷却期内不执行,但最后补一次"

1. 核心思想

每隔一定时间,最多执行一次。

但注意:我们实现的是带尾随执行(trailing)的节流 ,即在冷却期结束后,如果期间有触发,会补一次执行

就像技能有 CD(冷却时间),但如果你在 CD 期间一直按技能,CD 结束后会自动释放一次。

2. 代码实现与"尾随执行"逻辑

ini 复制代码
function throttle(fn, delay) {
  let last, deferTimer; // last上一次执行事件  deferTimer延迟执行的定时器
  return function() {
    let that = this;  
    let _args = arguments; // 类数组对象 保存所有参数
    let now = +new Date(); // 拿到当前时间戳  +强制类型转换 毫秒数

    if (last && now < last + delay) {
      // 处于冷却期 上次执行时间存在 且当前时间还没到下次允许执行的时间
      clearTimeout(deferTimer);
      deferTimer = setTimeout(function() {
        last = now;
        fn.apply(that, _args);
      }, delay);
    } else {
      // 已过冷却期,立即执行
      last = now;
      fn.apply(that, _args);
    }
  };
}

重点解析 if (last && now < last + delay) 分支:

  • 条件成立含义 :已经执行过至少一次(last 存在),且当前时间距离上次执行不足 delay 毫秒 → 正处于冷却期。
  • 但不能忽略这次触发!因为这可能是用户最后一次有效操作(比如完整输入了"react")。
  • 所以我们设置一个延迟定时器,计划在冷却期结束后执行。
  • clearTimeout(deferTimer) 的作用 :用户可能在冷却期内多次触发,但我们只关心最后一次,所以每次都要清除旧的定时器,只保留最新的。

3. 为什么需要"尾随执行"?

核心原因:避免丢失最后一次有效操作。

假设用户想搜 "react",在 200ms 内快速打完,而节流 delay = 500ms:

  • 简单节流(无尾随)

    • r(0ms)→ 立即执行
    • re(100ms)→ 被忽略
    • rea(150ms)→ 被忽略
    • react(200ms)→ 被忽略
      → 用户停止输入 但永远不会发送'react' 搜索框显示的是r的结果 而不是用户真正想搜的react
  • 带尾随的节流

    • r(0ms)→ 立即执行(last = 0
    • re(100ms)→ 冷却期,设 timer(600ms 执行)
    • rea(150ms)→ 更新 timer(650ms)
    • react(200ms)→ 更新 timer(700ms)
      → 用户停止输入后,在 700ms 自动执行 ajax('react')结果正确

4. 什么时候不需要尾随?

按钮防连点 :用户点击"提交"按钮,你希望 2 秒内只能点一次。

这种情况下,不需要在 2 秒后自动再提交一次 !此时应使用无尾随的简单节流


四、防抖 vs 节流:如何选择?

特性 防抖(Debounce) 节流(Throttle)
执行时机 停止触发后 delay ms 执行 每隔 delay ms 最多执行一次
是否保证最后一次 ✅ 是 ✅(带尾随时)
典型场景 搜索建议、窗口 resize 滚动加载、鼠标移动、按钮点击(防连点)
类比 回城技能(被打断重计时) 技能 CD(冷却后可再放)
  • 搜索建议 → 用防抖:用户输入是连续的,我们只关心最终结果。
  • 滚动加载 → 用节流:用户持续滚动,我们需要定期检查是否到底部,不能等到停止滚动才加载。

五、总结

防抖和节流虽然都是用于限制函数执行频率 ,但它们的触发逻辑和适用场景截然不同

  • 防抖 强调"只执行最后一次 ",适用于用户意图明确、操作连续的场景,如搜索、表单校验。
  • 节流 强调"定期执行 ",适用于高频但需周期性响应的场景,如滚动、拖拽、游戏帧更新。

而我们在实现节流时,特别加入了尾随执行(trailing) 机制,这是为了兼顾性能与用户体验------既避免了过度请求,又确保不会丢失用户的最终操作。

正如注释中所说:
"核心原因:避免丢失最后一次有效操作。"

通过合理运用闭包、定时器和上下文绑定,我们不仅实现了功能,还保证了代码的健壮性和可复用性。这些技巧,正是前端工程师在性能优化道路上的必备武器。


小提示 :在实际项目中,Lodash 等工具库已提供了成熟的 debouncethrottle 实现,支持更多选项(如 leadingtrailing 开关)。但理解其底层原理,才能在复杂场景中灵活应对。

希望本文能帮助你更清晰地掌握防抖与节流的本质。欢迎在评论区分享你的使用经验!

相关推荐
Zyx20072 小时前
React Hooks:函数组件的状态与副作用管理艺术
前端
让我上个超影吧2 小时前
基于SpringBoot和Vue实现CAS单点登录
前端·vue.js·spring boot
军军君012 小时前
Three.js基础功能学习五:雾与渲染目标
开发语言·前端·javascript·学习·3d·前端框架·three
程序员爱钓鱼2 小时前
Node.js 编程实战:RESTful API 设计
前端·后端·node.js
程序员爱钓鱼2 小时前
Node.js 编程实战:GraphQL 简介与实战
前端·后端·node.js
chilavert3183 小时前
技术演进中的开发沉思-284 计算机原理:二进制核心原理
javascript·ajax·计算机原理
罗技1233 小时前
Easysearch 集群监控实战(下):线程池、索引、查询、段合并性能指标详解
前端·javascript·算法
XiaoYu20023 小时前
第3章 Nest.js拦截器
前端·ai编程·nestjs
千寻girling3 小时前
面试官 : “ 说一下 Map 和 WeakMap 的区别 ? ”
前端·javascript·面试