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 吗?我认真想了一晚》

相关推荐
yuanyxh29 分钟前
Mac 软件推荐
前端·javascript·程序员
万少34 分钟前
AtomCode开发微信小程序《谁去呀》 全流程
前端·javascript·后端
某人辛木1 小时前
Web自动化测试
前端·python·pycharm·pytest
Kagol1 小时前
Superpowers GSD gstack AgentSkills深度测评
前端·人工智能
excel2 小时前
JavaScript 字符串与模板字面量:从表象到本质理解
前端
京东云开发者3 小时前
当AI成为导演-如何用AI创作动漫短剧
前端
李白的天不白3 小时前
使用 SmartAdmin 进行前后端开发
java·前端
乘风gg3 小时前
🤡PUA AI Coding 工具 的 10 条终极语录
前端·ai编程·claude
学Linux的语莫3 小时前
Vue 3 入门教程
前端·javascript·vue.js
怕浪猫4 小时前
第一章、Chrome DevTools Protocol (CDP) 详解
前端·javascript·chrome