防抖、节流
面试题
手写 debounce 和 throttle,说明它们分别适合什么场景
- 防抖:连续触发时只执行最后一次,核心是"重新计时"
- 节流:连续触发时固定时间内最多执行一次,核心是"控制频率"
面试官视角
这题不只是考 setTimeout,更常追问:
- 是否保留
this和参数 - 是否支持立即执行
- 是否能取消定时器
- 节流到底是时间戳版、定时器版,还是首尾都执行版
防抖 debounce
核心思路
每次触发都清掉上一次定时器,再重新开启一个定时器。只有在停止触发 wait 毫秒后,真正的函数才会执行
- 准备一个闭包变量
timer保存定时器 - 返回一个新函数,接收调用时的
this和参数 - 每次调用先
clearTimeout(timer) - 再开启新的
setTimeout,到时间后用fn.apply(this, args)执行 - 如果要支持立即执行,就在没有旧定时器时先执行一次,后续触发只负责刷新冷却时间
基础实现
function debounce(fn, wait = 300) {
let timer = null
return function (...args) {
const context = this
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
timer = null
fn.apply(context, args)
}, wait)
}
}支持立即执行和取消
function debounce(fn, wait = 300, immediate = false) {
let timer = null
function debounced(...args) {
const context = this
const shouldCallNow = immediate && timer === null
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
timer = null
if (!immediate) {
fn.apply(context, args)
}
}, wait)
if (shouldCallNow) {
return fn.apply(context, args)
}
}
debounced.cancel = function () {
if (timer) {
clearTimeout(timer)
timer = null
}
}
return debounced
}节流 throttle
核心思路
节流不是清空所有触发,而是在固定间隔内只允许执行一次。常见实现有两类:
| 实现 | 特点 |
|---|---|
| 时间戳版 | 第一次立即执行,最后一次不保证执行 |
| 定时器版 | 第一次延迟执行,最后一次通常能执行 |
| 首尾版 | 第一次立即执行,最后一次也尽量执行,面试更推荐 |
时间戳版
function throttle(fn, wait = 300) {
let lastTime = 0
return function (...args) {
const now = Date.now()
if (now - lastTime >= wait) {
lastTime = now
fn.apply(this, args)
}
}
}首尾都执行版
function throttle(fn, wait = 300) {
let lastTime = 0
let timer = null
function throttled(...args) {
const context = this
const now = Date.now()
const remaining = wait - (now - lastTime)
if (remaining <= 0) {
if (timer) {
clearTimeout(timer)
timer = null
}
lastTime = now
fn.apply(context, args)
return
}
if (!timer) {
timer = setTimeout(() => {
lastTime = Date.now()
timer = null
fn.apply(context, args)
}, remaining)
}
}
throttled.cancel = function () {
if (timer) {
clearTimeout(timer)
timer = null
}
lastTime = 0
}
return throttled
}使用场景
| 场景 | 选择 | 原因 |
|---|---|---|
| 搜索框联想 | 防抖 | 用户停止输入后再请求 |
| 窗口 resize 后重新布局 | 防抖 | 只关心最终尺寸 |
| 滚动加载、滚动埋点 | 节流 | 滚动过程中按频率执行 |
| 按钮防重复提交 | 防抖或锁 | 更关注只提交一次 |
| 鼠标拖拽位置计算 | 节流 | 控制计算频率 |
面试回答模板
防抖和节流都是为了控制高频事件。防抖强调"停下来再执行",实现上每次触发都清理旧定时器并重新计时;节流强调"固定频率执行",实现上记录上次执行时间或维护一个定时器,到时间才允许再次执行。写代码时要注意保留 this、参数、返回值,以及是否需要立即执行和取消能力
易错点
- 箭头函数返回包装函数会丢失调用时的
this,所以外层返回函数一般用普通函数 - 防抖的
immediate版本要在冷却结束后把timer置空,否则下一轮无法立即执行 - 节流时间戳版不保证最后一次执行,定时器版不保证第一次立即执行
- 面试中如果只写基础版,要主动补一句"生产版需要支持 cancel、leading、trailing"
