最后更新:2025-12-16
防抖(debounce)和节流(throttle)这俩概念,面试被问到的概率极高,但真正理解它们的人不多。不少人背了概念、写过代码,实际工作里却判断不清楚某个场景到底该用哪个。
我也是工作了一段时间之后才真正把这两个搞清楚。
先说清楚区别
防抖:触发事件后,等待 n 毫秒,如果期间没有再次触发就执行;如果又触发了,重新计时。
节流:不管你触发多少次,我在 n 毫秒内只执行一次,超过时间再触发才会再次执行。
一个类比:
- 防抖像电梯关门——有人进来就重新等,等到真的没人进来了才关门
- 节流像地铁——不管站台多少人,每隔固定时间发一班车
手写防抖
1 | function debounce(fn, delay) { |
使用:
1 | const handleSearch = debounce(function (keyword) { |
用户输入”前端开发”,每敲一个字就触发一次 input 事件,但只有停下来 500ms 之后才真正发请求。体验好,也不浪费资源。
手写节流
节流有两种常见实现,时间戳版和定时器版,各有细微差别:
1 | // 时间戳版:第一次立即执行,停止触发后不再执行 |
1 | // 定时器版:第一次延迟执行,停止触发后还会执行最后一次 |
工作里大多数场景用时间戳版就够了,如果你需要”停止操作后还要触发最后一次”,就用定时器版,或者把两个合并。
工作里真实遇到过的场景
搜索框输入联想
这是防抖的标准场景,上面例子就是。用节流在这里不合适——节流会在用户还在输入的时候就触发请求,拿到的关键词是残缺的。
防抖等用户”停”了再请求,体验更准确。
滚动加载更多
1 | const loadMore = throttle(function () { |
这里用节流。滚动事件触发频率极高(每秒几十次),用防抖的话,用户一直滚动就永远不触发,体验反而差。节流保证每 300ms 最多判断一次,够用了。
窗口 resize
1 | const handleResize = debounce(function () { |
resize 也是高频事件,但跟滚动不同——调整窗口大小是一个有明确”结束点”的操作,用防抖更合适,等用户停止拖动后再重新计算。
按钮防重复点击
1 | const submitBtn = document.getElementById('submit') |
这是我项目里比较常见的写法。防止用户手速快狂点提交按钮。当然更标准的做法是请求期间把按钮禁用,两者可以配合。
用 lodash 还是自己写?
工作项目里如果已经引了 lodash,直接用 _.debounce 和 _.throttle 就好,功能全、边界情况处理得好。
自己手写是为了理解原理,面试也会考。两个都掌握就行。
1 | import { debounce, throttle } from 'lodash-es' |
一个容易踩的坑
在 React 组件里用防抖,直接这么写会有问题:
1 | // ❌ 每次 render 都会创建新的 debounce 函数,防抖失效 |
每次 render 都创建了新的 debounce 实例,计时器自然不连续,防抖就没用了。
正确做法是用 useCallback 包一下,或者用 useRef 存起来:
1 | // ✅ useCallback 保证函数引用稳定 |
这个坑我自己也踩过,排查了半天发现防抖没生效,检查代码才发现是这个问题。





