从手写 debounce 到企业级实现:我在面试中如何“降维打击”面试官
你以为面试官在考你 debounce?
不,他在看你:工程能力 + 抽象能力 + 边界思维
一、面试官到底在考什么?
很多人一上来就开始写:
let timer = null
return function() {
clearTimeout(timer)
timer = setTimeout(fn, delay)
}
👉 面试官内心: “嗯,会背”
但他们真正想看的是:
1️⃣ 你有没有抽象能力
- debounce 的本质是什么?
- 为什么用闭包?
- timer 为什么要“复用”?
2️⃣ 你有没有工程思维
- 能不能支持
leading? - 能不能
cancel? - 有没有返回值?
- this 会不会丢?
3️⃣ 你有没有边界意识
- 连续触发怎么办?
- leading + trailing 同时存在?
- timer 会不会泄漏?
👉 总结一句话:
❌ 会写 debounce ≠ 会设计 debounce
二、基础版:面试第一步(原理表达)
先别写代码,先说清楚:
debounce 的核心是:
通过闭包保存 timer,每次触发时清除上一次定时器,重新计时,只执行最后一次
关键点:
- 闭包:保存 timer
- clearTimeout:取消上一次
- setTimeout:延迟执行
- apply:绑定 this + 传参
基础实现
function debounce(fn: Function, delay: number) {
let timer: ReturnType<typeof setTimeout> | null = null
return function (this: any, ...args: any[]) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
👉 到这里,只能算“及格”。
三、进阶:面试开始拉开差距
真正的加分点在这里👇
1️⃣ 支持 leading(立即执行)
场景:
- 用户点击按钮 → 立刻响应
- 后续防抖
👉 核心点:是否第一次触发
let isInvoked = false
2️⃣ 支持 trailing(停止后执行)
默认行为,但要能控制关闭
3️⃣ cancel 能力
面试官会问:
“如果用户离开页面,还要执行吗?”
👉 必须能手动取消
4️⃣ flush 能力(立即执行)
高级面试题:
“我不想等 delay,能不能马上执行?”
5️⃣ Promise 支持(高阶)
👉 这是很多人不会的点
让 debounce 有返回值
四、企业级 debounce(完整实现)
⚠️ 面试直接甩这个,基本可以结束战斗
type DebounceOptions = {
leading?: boolean
trailing?: boolean
}
type DebouncedFn<T extends (...args: any[]) => any> = {
(...args: Parameters<T>): Promise<ReturnType<T>>
cancel: () => void
flush: () => void
}
function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number,
options: DebounceOptions = {}
): DebouncedFn<T> {
let timer: ReturnType<typeof setTimeout> | null = null
let lastArgs: Parameters<T> | null = null
let lastThis: any = null
let isInvoked = false
const { leading = false, trailing = true } = options
let resolveList: ((value: ReturnType<T>) => void)[] = []
function invoke() {
if (!lastArgs) return
const result = fn.apply(lastThis, lastArgs)
resolveList.forEach(resolve => resolve(result))
resolveList = []
lastArgs = null
lastThis = null
isInvoked = true
}
const debounced = function (this: any, ...args: Parameters<T>) {
lastArgs = args
lastThis = this
return new Promise<ReturnType<T>>((resolve) => {
resolveList.push(resolve)
if (timer) clearTimeout(timer)
// leading
if (leading && !isInvoked) {
invoke()
}
timer = setTimeout(() => {
if (trailing && (!leading || isInvoked)) {
invoke()
}
timer = null
isInvoked = false
}, delay)
})
} as DebouncedFn<T>
debounced.cancel = () => {
if (timer) clearTimeout(timer)
timer = null
lastArgs = null
lastThis = null
resolveList = []
isInvoked = false
}
debounced.flush = () => {
if (timer) {
clearTimeout(timer)
invoke()
timer = null
}
}
return debounced
}
五、为什么这才是“企业级”实现?
我们来拆一下能力:
| 能力 | 是否支持 |
|---|---|
| 基础防抖 | ✅ |
| this 绑定 | ✅ |
| 参数透传 | ✅ |
| leading | ✅ |
| trailing | ✅ |
| cancel | ✅ |
| flush | ✅ |
| Promise 返回值 | ✅ |
| 内存安全 | ✅ |
👉 这已经接近 lodash.debounce 的设计了
六、真正的加分点:Hooks 化
如果你在 React 项目里这么说👇
“我在项目中封装了 useDebounce 来处理频繁状态更新”
面试官直接眼前一亮。
简版 useDebounce
import { useEffect, useState } from "react"
export function useDebounce<T>(value: T, delay: number) {
const [debouncedValue, setDebouncedValue] = useState(value)
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value)
}, delay)
return () => clearTimeout(timer)
}, [value, delay])
return debouncedValue
}
七、面试“杀招”:你要这样说
最后总结一句你可以直接背的👇
我在项目中不仅实现了基础 debounce,还做了企业级增强:
- 支持 leading / trailing 控制执行时机
- 提供 cancel / flush 控制生命周期
- 使用 Promise 统一返回值
- 通过闭包管理 timer,避免内存泄漏
- 并在 React 中封装为 useDebounce,优化频繁状态更新场景
整体设计参考了 lodash 和 ahooks 的实现思想
八、最后的启发(重点)
很多人学技术是这样的:
“这个 API 怎么用?”
但真正厉害的人在想:
“这个 API 为什么这么设计?”
debounce 本质考察的是:
- 闭包理解
- 事件模型
- 异步控制
- API 设计能力
- 边界处理能力
结尾
面试不会因为你写了 debounce 给你 offer
但会因为你怎么设计 debounce,决定你是:
- 初级工程师
- 还是能写基础库的人