← 返回目录

10. 防抖与节流

你在搜索框打字,每打一个字母就发一次请求?打"hello"五个字母就发五次?后端要被你打炸了。防抖节流就是用来给高频事件"踩刹车"的——不让它那么疯狂地触发。

一句话区分:防抖 = 停了才执行最后一次(你停笔了才查);节流 = 固定间隔执行一次(每隔固定时间查一次)。选错了,要么接口请求爆炸,要么体验卡顿。

10.1 互动演示(实时对比三种处理)

在输入框快速打字:
原始触发:0 防抖(300ms):0 节流(300ms):0
停止输入后看:防抖只执行最后一次,节流固定间隔执行。

10.2 知识点讲解

① 防抖(debounce):停了才执行最后一次

打个比方:你在电梯里按关门,每按一次它就重新开门等你——只要你还在按,它就不关。防抖就是这个思路:只要事件还在触发,就不执行,重新计时;你停下来了,它才执行最后一次

// 防抖函数:把你的业务函数包一层,返回一个新的"带刹车"的函数
function debounce(fn, delay = 300) {
  let timer = null; // 存定时器的编号

  // 返回一个新函数:以后调这个新函数,就有了防抖效果
  return function (...args) {
    clearTimeout(timer); // ★ 每次触发先把上一次的定时器取消!
    //  就像电梯门刚要关,你又按了一下,重新等
    timer = setTimeout(() => { // 重新开一个定时器,延迟 delay 毫秒后执行(箭头函数要写 ())
      fn.apply(this, args); // 到时间了,真正执行业务函数
      timer = null;
    }, delay);
  };
}

// 用法:
function searchHandler(val) {
  console.log("搜索了:" + val); // 实际开发这里发 AJAX 请求
}
// 把 searchHandler 包成防抖版,300 毫秒内连续触发只执行最后一次
const debouncedSearch = debounce(searchHandler, 300);

// 输入框绑定防抖版,快速打"hello"只发一次请求
document.getElementById("inpSearch").oninput = function () { // 注意 id 要和 HTML 里的一致
  debouncedSearch(this.value);
};

如果你在输入框直接绑 searchHandler,打五个字母发五次请求——因为没有刹车,每按一个键就触发一次。用防抖包一层后,你停下来 300 毫秒才发一次,接口压力小多了。

② 节流(throttle):固定间隔执行一次

节流的思路不同:不管你触发多频繁,我每隔固定时间只执行一次。就像闹钟,你不按它也会每隔固定时间响一次——不是"停了才响",而是"每隔一段时间响一次"。

// ===== 版本一:时间戳版(第一次立刻执行) =====
function throttle(fn, interval = 300) {
  let lastTime = 0; // 上一次执行的时间
  return function (...args) {
    const now = Date.now(); // 现在的时间戳
    // 如果距上次执行够间隔了,就执行
    if (now - lastTime >= interval) {
      fn.apply(this, args);
      lastTime = now; // 更新上次执行时间
    }
  };
}

// ===== 版本二:定时器版(第一次延迟执行) =====
function throttleTimer(fn, interval = 300) {
  let timer = null;
  return function (...args) {
    if (timer) return; // 已经有定时器在排队了,就不动
    timer = setTimeout(() => { // 箭头函数要写 ()
      fn.apply(this, args);
      timer = null; // 执行完了,解锁
    }, interval);
  };
}

两个版本区别:时间戳版——第一次触发立刻执行,停止后不再执行最后一次;定时器版——第一次延迟执行,停止后还会执行最后一次。看场景选。

③ 防抖 vs 节流速查表

对比维度防抖 debounce节流 throttle
核心逻辑频繁触发重置计时,停了才执行固定间隔强制执行一次
执行时机停止触发后延迟执行(延后)时间戳版:首次立即;定时器版:延后
触发频率只执行最后一次匀速分段执行
典型效果输入停半秒才搜索滚动时每隔一段距离加载
关键词clearTimeout 重置定时器记录时间戳 / 锁住定时器

④ 四大场景怎么选

// 1. 输入框实时联想搜索 → 用防抖!
//  你打字时不需要每打一个字母就搜一次,停下来再搜
//  减少接口请求次数,用户体验也好

// 2. 窗口缩放 resize → 用防抖!
//  你拖窗口边框时会疯狂触发 resize,松手了再重新算布局

// 3. 页面滚动下拉加载 → 用节流!
//  滚动是持续触发的,你需要每隔固定时间检测一次位置
//  不能等用户停了再加载——那样用户滚到中间啥都没有

// 4. 提交按钮防重复点击 → 用节流/短时防抖!
//  用户快速点好几下提交,只发一次请求,防止提交多条数据

口诀:只需要结束后执行一次 → 防抖;需要过程中均匀执行 → 节流

⑤ 实战:用在哪 / 常见坑 / 怎么解决

可能在什么地方用:

① 搜索框输入联想(防抖);② 窗口拖动缩放重新布局(防抖);③ 页面滚动到底自动加载更多(节流);④ 按钮防重复提交(节流/防抖)。

常见的问题:

① 防抖里忘了 clearTimeout,导致每次还是都执行;② 节流用在搜索框上——刚打第一个字就立刻发了请求,用户还没打完就白跑一趟;③ 防抖用在滚动加载上——结果用户不滚了才加载,体验很差;④ 延迟时间设太长,用户等了一秒多才出结果。

解决思路:

① 防抖核心就是 clearTimeout(timer) 重置计时;② 搜索框用防抖(300ms 合适);③ 滚动加载用节流(200~500ms 合适);④ 按钮防重可以在提交期间把按钮 disabled。

一句话:防抖 = 停了才执行最后一次(搜索/resize);节流 = 固定间隔执行一次(滚动/按钮防重)。

10.3 课后作业(融入本节)

练习一:防抖测试——① 封装 debounce 函数;② 做一个输入框,分别测试不加防抖和加防抖(300ms)时快速输入"hello"的打印次数差异。

练习二:节流测试——① 封装 throttle 函数;② 做一个 div,绑 mousemove 事件,分别测试不加节流和加节流(100ms)时鼠标移动的打印频率差异。

练习四:按钮防重复提交——创建一个提交按钮,点击时 setTimeout 模拟 2 秒提交。快速点好几次,看发了几次请求。再用防抖/节流改造,对比哪种更合适。