防抖与节流:如何让频繁触发的函数 “慢下来”?

在前端开发中,有些事件会被 "高频触发"------ 比如输入框打字时每秒触发多次keyup,滚动页面时每秒触发几十次scroll,快速点击按钮时瞬间触发多次click。如果每次触发都执行复杂逻辑(如发送请求、计算布局),会严重拖慢页面,甚至导致卡顿。

防抖(debounce)和节流(throttle)是解决这类问题的两种经典方案。它们通过不同的策略控制函数执行频率,既能保证功能正常,又能大幅提升性能。

防抖(debounce):等 "安静" 下来再执行

(1)核心逻辑:短时间内多次触发,只执行最后一次

防抖的规则很简单:当函数被连续触发时,只有在停止触发后等待指定时间(delay),才会执行一次;如果在等待期间再次触发,就重新计时。 举个例子(delay=500ms):

  • 用户在输入框快速打字,每次按键都会触发事件,但防抖会 "推迟" 执行,直到用户停手 500ms 后,才执行一次搜索请求;
  • 如果用户在 300ms 内又按了下一个键,之前的计时会被取消,重新从 0 开始算 500ms。

生活类比:像是电梯关门 ------ 如果有人连续进入,电梯会不断推迟关门时间,直到最后一个人进入后,才会关门运行。

(2)实现代码与关键细节

javascript 复制代码
function debounce(fn, delay) {
  // 用闭包保存定时器ID,确保多次触发时能访问到同一个定时器
  let timer = null;

  // 返回一个新函数,接收触发时的参数
  return function (...args) {
    const that = this; // 保存当前上下文(如DOM元素)

    // 如果已有定时器,先清除(重新计时)
    if (timer) {
      clearTimeout(timer);
    }

    // 重新设置定时器,delay毫秒后执行原函数
    timer = setTimeout(() => {
      // 用call确保原函数的this指向正确(如绑定到触发事件的DOM元素)
      fn.call(that, ...args);
      // 执行后清空定时器(非必需,但逻辑更清晰)
      timer = null;
    }, delay);
  };
}

关键细节:

  • 闭包的应用:通过timer变量在多次触发间共享状态,实现 "清除上一次定时器" 的逻辑;
  • this指向修正:用call(that, ...args)确保原函数内部的this指向正确(比如事件处理函数中,this应指向触发事件的 DOM 元素);
  • 参数传递:用扩展运算符...args接收所有参数,保证原函数能拿到触发时的参数(如输入框的value)。

(3)使用场景与实战示例

适用场景

  • 输入框实时搜索 / 联想:等待用户输入停顿后再发请求,减少接口调用次数;
  • 窗口resize事件:窗口调整完成后再计算元素布局,避免多次重排;
  • 按钮防重复提交:用户快速点击按钮时,只在最后一次点击后执行提交逻辑。

实战代码(输入框)

html 复制代码
<input type="text" id="debounceInput" placeholder="防抖示例:输入后停顿500ms执行">

<script>
  // 防抖函数(同上)
  function debounce(fn, delay) { /* ... */ }

  // 模拟搜索请求
  function search(content) {
    console.log(`[防抖] 搜索内容:${content}`);
  }

  // 生成防抖处理后的搜索函数(延迟500ms)
  const debouncedSearch = debounce(search, 500);

  // 绑定输入框事件
  document.getElementById('debounceInput').addEventListener('keyup', function(e) {
    debouncedSearch(e.target.value);
  });
</script>

节流(throttle):固定间隔内必须执行一次

(1)核心逻辑:无论触发多频繁,固定间隔内只执行一次

节流的规则是:函数被触发后,立即执行一次;之后在指定时间(delay)内,无论触发多少次,都不会执行;直到 delay 时间过去,再次触发时才会执行第二次

举个例子(delay=1000ms):

  • 第一次触发时,立即执行函数,同时记录执行时间;
  • 接下来 1 秒内,无论触发多少次,都不执行;
  • 1 秒后再次触发,立即执行,并更新记录时间,以此类推。

与防抖的核心区别

  • 防抖:等待 "完全停止触发" 后才执行,可能长时间不执行;
  • 节流:固定间隔内 "必须执行一次",保证函数有规律地执行。

实现代码与关键细节

javascript 复制代码
function throttle(fn, delay) {
  let lastTime = 0; // 记录上一次执行的时间(初始为0)
  let timer = null; // 用于延迟执行的定时器

  return function (...args) {
    const that = this;
    const now = Date.now(); // 当前时间戳

    // 如果距离上一次执行不足delay,设置延迟执行
    if (now - lastTime < delay) {
      // 清除之前的定时器,避免重复延迟执行
      if (timer) clearTimeout(timer);
      timer = setTimeout(() => {
        lastTime = Date.now(); // 更新执行时间
        fn.call(that, ...args);
        timer = null;
      }, delay - (now - lastTime)); // 计算剩余时间
    } else {
      // 距离上一次执行超过delay,立即执行
      lastTime = now;
      fn.call(that, ...args);
    }
  };
}

关键细节:

  • 时间戳判断:通过now - lastTime计算与上次执行的间隔,决定是否立即执行;
  • 延迟执行兜底:当触发间隔小于 delay 时,用定时器保证 "在 delay 后必须执行一次"(避免因持续高频触发导致函数一直不执行);
  • 闭包保存状态:lastTimetimer在多次触发间共享,确保间隔计算准确。

(3)使用场景与实战示例

适用场景

  • 滚动事件scroll:计算滚动位置、加载懒加载图片时,每秒执行 1-2 次即可,无需高频触发;

  • 鼠标移动mousemove:拖拽元素时,固定间隔更新位置,避免过度计算;

  • 高频点击按钮:如游戏中的攻击按钮,限制每秒最多触发 5 次,防止操作过快。

实战代码

html 复制代码
<input type="text" id="throttleInput" placeholder="节流示例:每1000ms最多执行一次">

<script>
  // 节流函数(同上)
  function throttle(fn, delay) { /* ... */ }

  // 模拟搜索请求
  function search(content) {
    console.log(`[节流] 搜索内容:${content}`);
  }

  // 生成节流处理后的搜索函数(间隔1000ms)
  const throttledSearch = throttle(search, 1000);

  // 绑定输入框事件
  document.getElementById('throttleInput').addEventListener('keyup', function(e) {
    throttledSearch(e.target.value);
  });
</script>

防抖与节流的核心区别与选择指南

特性 防抖(debounce) 节流(throttle)
执行时机 停止触发后等待 delay 执行一次 触发后立即执行,之后固定间隔执行
适用场景 等待 "完成" 后执行(如输入完成) 需要 "定期" 执行(如滚动计算)
极端情况 若一直触发,可能永远不执行 无论是否一直触发,固定间隔必执行

选择原则

  • 若需要 "操作完成后执行一次"(如搜索输入),用防抖;
  • 若需要 "操作过程中有规律地执行"(如滚动加载),用节流。

背后的核心知识点:闭包与高阶函数

防抖和节流的实现都依赖两个关键概念:

  1. 高阶函数debouncethrottle都是高阶函数 ------ 它们接收一个函数(fn)作为参数,并返回一个新函数。这使得它们能对原函数进行 "包装",添加额外的控制逻辑(如定时器)。
  2. 闭包 :返回的新函数通过闭包访问timerlastTime等变量,这些变量在多次触发间保持状态,实现 "清除定时器""计算时间间隔" 等核心逻辑。如果没有闭包,就需要将这些状态暴露为全局变量,导致代码污染和逻辑混乱。
相关推荐
海天胜景4 小时前
vue3 el-table动态表头
javascript·vue.js·elementui
G_whang4 小时前
jenkins自动化部署前端vue+docker项目
前端·自动化·jenkins
凌辰揽月6 小时前
AJAX 学习
java·前端·javascript·学习·ajax·okhttp
鱼樱前端8 小时前
2025前端人一文看懂 Broadcast Channel API 通信指南
前端·vue.js
烛阴8 小时前
非空断言完全指南:解锁TypeScript/JavaScript的安全导航黑科技
前端·javascript
鱼樱前端8 小时前
2025前端人一文看懂 window.postMessage 通信
前端·vue.js
快乐点吧9 小时前
【前端】异步任务风控验证与轮询机制技术方案(通用笔记版)
前端·笔记
pe7er9 小时前
nuxtjs+git submodule的微前端有没有搞头
前端·设计模式·前端框架
七月的冰红茶9 小时前
【threejs】第一人称视角之八叉树碰撞检测
前端·threejs