Writing

防抖与节流

文章發表於

前言

想象一下,当你在某个网站通过搜索框寻找东西时,你输入的每一个字都会立即触发一次自动推荐(auto suggest)的 API,这样的使用体验可能会让用户直接离开,转向竞品。

虽然功能看似简单,但如果没有妥善处理,可能会造成一连串的问题:

  • 服务器过载:过于频繁的 API 请求会对服务器造成巨大的负担。如果后端没有做流量管控,甚至可能导致服务器崩溃。
  • 竞态条件(Race Condition):例如,你先输入「apple is」,接着立刻改成「banana」,由于请求响应的时间差,画面上可能同时有机会出现「apple is」或「banana」的推荐结果。
  • 界面卡顿:当每输入一个字就立即更新结果,画面会不停地在「加载中」与「显示结果」之间闪烁切换,造成突兀且不舒服的使用体验。

这篇文章将会介绍两种常见的解决方案,来解决类似的问题:防抖(Debounce)和节流(Throttle)。

节流

什么是节流?

节流是一种让函数执行「平均分布在时间轴上」的技巧,无论触发事件的频率有多高,节流确保函数在指定的间隔内最多被调用一次,例如设定每秒执行一次,那即使事件连续被触发 100 次,也只会每秒处理一次,节奏是固定的。

核心原理

  • 第一次函数调用时立即执行
  • 设定一个冷却期(cooling period)
  • 在冷却期内,忽略或记录新的函数调用
  • (选用) 冷却期结束后,可以再次执行上一个函数

使用场景

节流适合用在「需要稳定执行频率」的情境,例如:

  • 滚动事件:限制滚动事件处理频率
  • 窗口调整大小:限制调整大小时的重新计算
  • 鼠标移动:限制鼠标移动事件处理频率
  • 游戏控制:限制按键事件处理频率
  • 拖拽和绘图操作:确保平滑的视觉效果

Throttle 的实现

这个基本实现在大多数简单场景中已经足够,比如限制按钮点击或基本的滚动事件处理。

function throttle(func, wait) {
let shouldThrottle = false;
return function (...args) {
if (shouldThrottle) return;
shouldThrottle = true;
func.apply(this, args);
setTimeout(() => {
shouldThrottle = false;
}, wait);
};
}

防抖

什么是防抖?

防抖是另一种控制函数执行频率的技术,它保证函数在指定时间「最后一次」触发后才执行一次。简单来说,只有在一段时间内没有新的触发时,才真正执行函数;节流则是让函数以固定频率执行,即使中间有很多触发,也只会定期处理一次。

核心原理

  • 第一次触发时,设定一个冷却期(cooling period)
  • 在冷却期内,若有新的触发,就取消上一次的计时器并重置冷却期
  • 冷却期结束后,执行函数,并且清除计时器

使用场景

防抖特别适合用在「等待用户停下来再做事」的情境,换句话说就是,它最适合应对那种短时间内会被大量触发的事件,但我们其实只在意「最后一次」的状况。

  • 搜索建议:用户暂停输入后才发起搜索请求。
  • 表单验证:输入框停止输入后才触发验证。
  • 自动保存:用户停止编辑一段时间后才自动保存。

防抖的实现

function debounce(func, wait) {
let timeId = null;
return function (...args) {
if (timeId) {
clearTimeout(timeId);
}
timeId = setTimeout(() => {
func.apply(this, args);
}, wait);
};
}

节流 & 防抖比较

模式(Pattern)使用时机(Use When)示例(Example)
防抖(Debounce)只关心用户停止操作后的最终值搜索建议、表单验证、自动保存
节流(Throttle)需要在操作过程中定期获取更新滚动位置追踪、窗口大小变化处理、进度更新
如果您喜欢这篇文章,请点击下方按钮分享给更多人,这将是对笔者创作的最大支持和鼓励。
Buy me a coffee