← 返回目录

10. 防抖与节流

防抖就是频繁触发只执行最后一次,节流就是固定频率执行

核心区别:
1. 防抖 debounce——一直输入不执行,停下来才执行(搜索框)
2. 节流 throttle——每隔 N 毫秒执行一次(滚动加载)

坑:防抖用 setTimeout + clearTimeout、节流用时间戳或定时器、resize/scroll/input 事件必加

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

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

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

10.2 知识点讲解

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

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

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

// 用法:
function searchHandler(val) {
  // 实际开发这里发 AJAX 请求
  console.log("搜索了:" + val);
}

// 把 searchHandler 包成防抖版,300 毫秒内连续触发只执行最后一次
// 输入框绑定防抖版,快速打"hello"只发一次请求
const debouncedSearch = debounce(searchHandler, 300);
// 注意 id 要和 HTML 里的一致
document.getElementById("inpSearch").oninput = function () {
  debouncedSearch(this.value);
};

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

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

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

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

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

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

3. 防抖 vs 节流速查表

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

4. 四大场景怎么选

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

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

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

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

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

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

可能在什么地方用:

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

常见的问题:

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

解决思路:

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

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

10.3 课后作业(融入本节)

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

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

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

本页重点

API作用参数返回值代码示例
setTimeout(fn,ms)过一会再执行一次入参:函数、毫秒返回:定时器 idlet id = setTimeout(function () { }, 1000);
clearTimeout(id)取消还没到点的定时入参:定时器 id返回:无clearTimeout(id);
debounce(fn,ms)防抖:停手后才执行(输入联想)入参:函数、毫秒返回:新函数let search = debounce(fn, 300);
throttle(fn,ms)节流:按节奏固定执行(滚动)入参:函数、毫秒返回:新函数let scroll = throttle(fn, 200);