skip to content
月与羽

防抖和节流

/ 11 min read

好的,我们来详细解释一下前端开发中非常重要的两个性能优化概念:防抖节流

它们的核心目的都是解决高频事件(如 input, scroll, resize, mousemove, click 等)触发过于频繁,导致事件处理函数被大量执行,从而造成性能问题(如卡顿、假死、服务器压力过大)

简单来说:

  • 防抖: 事件触发后,等待一段时间(比如 500ms)再执行处理函数。如果在这段等待时间内,事件又被触发了,则重新开始计时。 目的是确保事件处理函数只在用户停止操作后执行一次
  • 节流: 事件触发后,在固定的时间间隔内(比如 200ms),只允许执行一次处理函数。无论事件在这个间隔内触发了多少次,都只在间隔结束时执行一次(或者只在间隔开始时执行一次)。目的是确保事件处理函数以固定的频率执行

一、防抖

1. 核心思想

“延迟执行 + 重置计时”。就像电梯门:你走进电梯,门开始关闭(开始计时),如果又有人进来,门会重新打开并重新开始关闭(重置计时)。直到最后一个人进来后,门完全关闭(等待时间结束),电梯才启动(执行函数)。

2. 工作原理

  1. 事件触发时,设置一个定时器setTimeout),在指定延迟时间(如 delay 毫秒)后执行事件处理函数。
  2. 如果在定时器结束之前,事件又被触发了一次
    • 清除之前设置的定时器clearTimeout)。
    • 重新设置一个新的定时器,再次等待 delay 毫秒。
  3. 只有当事件delay 毫秒内都没有再次触发时,定时器才会最终执行事件处理函数。

3. 关键特点

  • 执行时机: 在事件停止触发后的 delay 毫秒执行。
  • 执行次数: 在一次连续触发过程中,最多只执行一次(在触发完全停止后)。
  • 适用场景: 只关心最终状态,不关心中间过程。

4. 典型应用场景

  • 搜索框输入联想: 用户在输入框快速打字时,不应该每次按键都去服务器请求搜索建议(太浪费资源)。应该等用户停止输入一小段时间(如 300ms)后,再发送请求获取最终输入内容的建议。
  • 文本输入验证: 实时验证用户输入(如用户名格式、邮箱格式)时,不需要每次按键都验证,等用户输完一个词或停止输入后再验证。
  • 窗口大小调整: 监听 resize 事件计算新布局时,拖动窗口边缘会触发大量事件。用防抖可以只在用户停止调整窗口大小后才执行一次布局计算。
  • 按钮防重复提交: 防止用户在短时间内多次点击同一个提交按钮(虽然节流也能用,但防抖更强调“停止操作后执行一次”)。

5. 简单实现示例 (JavaScript)

function debounce(func, delay) {
let timeoutId; // 存储定时器ID
return function(...args) {
// 清除之前的定时器(如果存在)
clearTimeout(timeoutId);
// 设置新的定时器
timeoutId = setTimeout(() => {
// delay 时间后,执行原始函数,并绑定正确的 this 和参数
func.apply(this, args);
}, delay);
};
}
// 使用示例
const handleInput = debounce((event) => {
console.log('执行搜索或验证:', event.target.value);
// 这里可以放置实际的搜索请求或验证逻辑
}, 300); // 300ms 延迟
document.getElementById('searchInput').addEventListener('input', handleInput);

二、节流

1. 核心思想

“固定频率执行”。就像滴水的水龙头:无论你把水龙头开多大(事件触发多快),水滴(函数执行)总是以固定的间隔(比如每秒一滴)落下。

2. 工作原理

常见实现方式有两种:

  • 时间戳方式:

    1. 记录上一次执行函数的时间戳 lastTime
    2. 事件触发时,获取当前时间戳 now
    3. 如果 now - lastTime >= delay,则立即执行函数,并更新 lastTime = now
    4. 否则,忽略本次触发。
    • 特点: 第一次触发会立即执行,之后每隔 delay 毫秒执行一次。
  • 定时器方式:

    1. 设置一个标志位 canRun = true
    2. 事件触发时,如果 canRuntrue
      • 设置 canRun = false
      • 立即执行函数。
      • 设置一个定时器,在 delay 毫秒后将 canRun 重置为 true
    3. 如果 canRunfalse,则忽略本次触发。
    • 特点: 第一次触发会立即执行,之后每隔 delay 毫秒执行一次(在定时器回调中执行)。

3. 关键特点

  • 执行时机: 以固定的时间间隔执行(如每 200ms 执行一次)。
  • 执行次数: 在一次连续触发过程中,会执行多次,但频率被限制(如 1 秒触发 50 次,节流后可能只执行 5 次)。
  • 适用场景: 需要以固定频率执行,关心中间过程的状态。

4. 典型应用场景

  • 页面滚动事件: 监听 scroll 事件实现懒加载图片、固定导航栏、返回顶部按钮显示/隐藏等。滚动时事件触发极其频繁,用节流可以每隔一段时间(如 200ms)检查一次滚动位置,而不是每次滚动像素都检查,保证流畅性。
  • 鼠标移动事件: 监听 mousemove 实现拖拽、绘制、跟随鼠标的提示框等。鼠标移动事件触发非常密集,节流可以限制拖拽或绘制的更新频率,避免卡顿。
  • 高频点击: 防止用户疯狂点击按钮(如点赞、收藏),虽然防抖也能用,但节流能保证在一定时间内至少响应一次点击,体验可能更好(用户感觉按钮“有反应”)。
  • 游戏循环/动画帧: 在需要控制更新频率的场景(虽然 requestAnimationFrame 更适合动画,但节流可用于其他逻辑)。

5. 简单实现示例 (JavaScript - 定时器方式)

function throttle(func, delay) {
let canRun = true; // 执行标志位
return function(...args) {
if (!canRun) return; // 如果正在等待,则直接返回
canRun = false; // 锁定
// 立即执行函数
func.apply(this, args);
// 设置定时器,delay 时间后解锁
setTimeout(() => {
canRun = true;
}, delay);
};
}
// 使用示例
const handleScroll = throttle(() => {
console.log('执行滚动相关逻辑,如检查位置:', window.scrollY);
// 这里可以放置实际的懒加载、固定导航等逻辑
}, 200); // 200ms 间隔
window.addEventListener('scroll', handleScroll);

三、防抖 vs 节流:核心区别总结

特性防抖节流
核心思想延迟执行,触发则重置计时固定频率执行
执行时机停止触发后等待 delay 毫秒执行每隔 delay 毫秒 执行一次
执行次数连续触发中最多执行一次(在停止后)连续触发中会执行多次(按固定频率)
关注点最终状态(用户操作完成后的结果)中间过程(以可控频率更新状态)
比喻电梯门(等人停了才关门)滴水龙头(固定频率出水)
典型场景搜索联想、输入验证、窗口调整、防重复提交滚动事件、鼠标移动、高频点击、游戏循环

四、如何选择?

  • 当你只关心用户操作的最终结果时,用防抖。
    • 例如:搜索框输入(等用户打完字再搜)、表单验证(等输完一个字段再验)。
  • 当你需要以固定频率更新状态或响应用户操作时,用节流。
    • 例如:滚动加载(每隔一段距离检查一次)、鼠标拖拽(平滑移动)、防止疯狂点击(让按钮有响应感)。

简单记忆:

  • 防抖 = 等一等,别急。 (等用户停手)
  • 节流 = 匀速走,别跑。 (控制速度)

理解并正确使用防抖和节流,是提升前端应用性能和用户体验的重要手段。它们是前端开发者工具箱中非常实用的优化利器!