JavaScript 防抖和节流的区别是什么?如何实现?

前几天,我在写一个搜索功能时碰到了一个小问题:用户每输入一个字母,页面就立刻发一次请求。

只是轻轻的打个手机两个字,后台收到了四次查询,这样子服务器的压力可不小啊。

于是就想到了JS的防抖(Debounce)节流(Throttle)

这是两种常用的性能优化技术,主要用于控制函数的执行频率,比如 resizescrollinput 等情况。


概念区别

特性 防抖(Debounce) 节流(Throttle)
核心思想 在连续触发事件时,只在最后一次触发后等待指定时间再执行 在连续触发事件时,保证在指定时间间隔内只执行一次
适用场景 搜索框输入、窗口 resize 后只执行一次计算 滚动加载、按钮点击限制、鼠标移动追踪等
是否立即执行 可选(可实现"立即执行"或"延迟执行") 通常首次立即执行,之后按固定间隔

防抖(Debounce)实现

举个例子:

你在淘宝搜索"蓝牙耳机"的时候:

  • 打"蓝" → 不急着搜
  • 打"蓝牙" → 还是不搜
  • 打完"蓝牙耳机"并停顿 0.5 秒 → 此时才发起搜索请求

这样既省流量,又避免服务器压力过大。

基础版本(延迟执行)

js 复制代码
function debounce(func, delay) {
  let timer = null; // 用来记录倒计时的变量

  return function (...args) {
    // 每次触发,先取消之前的倒计时
    clearTimeout(timer);

    // 重新开始一个新的倒计时
    timer = setTimeout(() => {
      func.apply(this, args); // 时间到了,执行真正的函数
    }, delay);
  };
}

使用示例:搜索框优化

html 复制代码
<input id="search" placeholder="请输入关键词" />
js 复制代码
const input = document.getElementById('search');

// 包装一个"防抖版"的搜索函数
const debouncedSearch = debounce(function(e) {
  console.log('正在搜索:', e.target.value);
  // 这里可以调用 API 发请求
}, 500); // 500毫秒 = 0.5秒

input.addEventListener('input', debouncedSearch);

实现效果:用户快速输入"hello"时,只在输入完"o"并停顿0.5秒后才发送请求。

而不是输入h、e、l、l、o时发送5次请求!

进阶版本(支持立即执行)

js 复制代码
function debounce(func, delay, immediate = false) {
  let timer = null;
  return function (...args) {
    // 判断是否应该"立即执行"
    const callNow = immediate && !timer;

    // 清除之前的定时器(防抖核心)
    clearTimeout(timer);

    // 设置新的延迟定时器
    timer = setTimeout(() => {
      timer = null; // 定时器执行完后重置
      if (!immediate) func.apply(this, args); // 如果不是立即模式,就在延迟后执行
    }, delay);

    // 如果是立即模式且当前没有 pending 的定时器,则立刻执行
    if (callNow) func.apply(this, args);
  };
}

参数说明:

  • immediate: true:第一次触发时立即执行,之后在停止触发 delay 时间后不再执行。
  • immediate: false(默认):只有在停止触发 delay 时间后才执行。

使用场景:

  • immediate: true:按钮提交,第一次点击立即执行,防止连续点击。
  • immediate: false:搜索框,等用户输入完成再搜索。

节流(Throttle)实现

举个例子:

你在玩一个"自动存档"的游戏:

  • 游戏每 5 秒自动保存一次进度;
  • 即使你狂按"存档键",系统也只认每 5 秒那次。

基础代码实现(时间戳版)

js 复制代码
function throttle(func, delay) {
  let lastTime = 0; // 上次执行的时间

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

    // 如果距离上次执行已经超过 delay 毫秒
    if (now - lastTime >= delay) {
      func.apply(this, args); // 执行函数
      lastTime = now;         // 更新"上次执行时间"
    }
  };
}

使用示例:监听页面滚动

js 复制代码
const throttledScroll = throttle(function() {
  console.log('页面滚动了!当前滚动位置:', window.scrollY);
}, 300); // 每300毫秒最多执行一次

window.addEventListener('scroll', throttledScroll);

无论你怎么疯狂滚动,函数每 0.3 秒最多执行一次,大大减少计算量。

定时器方式(延迟执行)

js 复制代码
function throttle(func, delay) {
  let timer = null;
  return function (...args) {
    if (!timer) {
      timer = setTimeout(() => {
        func.apply(this, args);
        timer = null;
      }, delay);
    }
  };
}

第一次触发也要等待delay时间后才执行

更完善的节流(首尾都执行)

js 复制代码
function throttle(func, delay) {
  let timer = null;
  let lastTime = 0;
  
  return function (...args) {
    const now = Date.now();
    const remaining = delay - (now - lastTime);

    if (remaining <= 0 || remaining > delay) {
      // 距离上次执行时间已超过delay,立即执行
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      func.apply(this, args);
      lastTime = now;
    } else if (!timer) {
      // 设置定时器,在剩余时间后执行
      timer = setTimeout(() => {
        func.apply(this, args);
        timer = null;
        lastTime = Date.now();
      }, remaining);
    }
  };
}

这个版本的优点:

  • 第一次触发立即执行(用户体验好)
  • 最后一次触发也确保执行(不丢失重要操作)
  • 中间按固定频率执行(性能优化)

适用场景: 需要同时保证响应性和性能的场景,如实时数据更新、动画效果等。


使用示例

1. 滚动加载优化

javascript 复制代码
const checkScroll = throttle(() => {
  const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
  
  // 距离底部100px时加载更多
  if (scrollTop + clientHeight >= scrollHeight - 100) {
    loadMoreContent();
  }
}, 250);

window.addEventListener('scroll', checkScroll);

2. 窗口调整的响应式处理

javascript 复制代码
const handleResize = debounce(() => {
  // 重新计算布局
  calculateLayout();
  // 更新图表尺寸
  updateCharts();
}, 300);

window.addEventListener('resize', handleResize);

总结

如何选择?

场景 推荐技术 原因
搜索框输入 防抖 只关心用户最终输入的内容
窗口调整 防抖 只关心调整结束后的最终尺寸
页面滚动 节流 需要实时响应但又要控制频率
按钮点击 节流 防止重复提交,但要给用户及时反馈
鼠标移动 节流 需要实时跟踪但避免过于频繁

防抖 :等你不再操作了我才执行。 节流:再怎么频繁,我也按节奏来。

两者都能减少函数调用次数,提升性能,但适用场景不同,可以根据需求选择。

本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《前端高手才知道的秘密:Blob 居然这么强大》

《这 5 个冷门 HTML 标签,让我直接删了100 行 JS 代码》

《重构了20个SpringBoot项目后,总结出这套稳定高效的架构设计》

《代码里全是 new 对象,真的很 Low 吗?我认真想了一晚》

相关推荐
脩衜者3 分钟前
极其灵活且敏捷的WPF组态控件ConPipe 2026
前端·物联网·ui·wpf
Mike_jia8 分钟前
Dockge:轻量开源的 Docker 编排革命,让容器管理回归优雅
前端
GISer_Jing15 分钟前
前端GEO优化:AI时代的SEO新战场
前端·人工智能
没想好d17 分钟前
通用管理后台组件库-4-消息组件开发
前端
文艺理科生19 分钟前
Google A2UI 解读:当 AI 不再只是陪聊,而是开始画界面
前端·vue.js·人工智能
晴栀ay21 分钟前
React性能优化三剑客:useMemo、memo与useCallback
前端·javascript·react.js
JS_GGbond21 分钟前
JavaScript继承大冒险:从“原型江湖”到“class殿堂”
前端
XiaoYu200222 分钟前
第6章 Postgres数据库安装
前端·postgresql
洛卡卡了22 分钟前
从活动编排到积分系统:事件驱动在业务系统中的一次延伸
前端·后端·面试
知其然亦知其所以然24 分钟前
别再死记硬背了,一篇文章搞懂 JS 乘性操作符
前端·javascript·程序员