算法 - 节流 (Throttle) 手写实现
1. 题目
实现一个节流函数 throttle(fn, wait):
- 事件被频繁触发时,每
wait毫秒最多执行一次fn。 - 与防抖不同:节流不要求「安静下来」,而是给执行限速,保证一段时间内至少/至多按节奏跑。
- 保留正确的
this与参数。
典型场景:滚动加载、拖拽、游戏连续射击、鼠标 mousemove 计算、大屏 resize。
示例:
1 | 滚动页面每 5ms 触发一次 scroll |
2. 解题思路
节流有两种主流实现,各自优缺点分明,面试最好都会写。
2.1 时间戳版(保证间隔,可能丢失最后一次)
记录上次执行时间 last。每次触发比较 now - last >= wait:满足就执行并更新 last;否则这次丢弃。
- 优点:首次触发立即执行(leading)。
- 缺点:如果最后一次触发落在间隔内,会被丢弃,即「尾巴」不执行(trailing 缺失)。
2.2 定时器版(保证最后一次会执行)
用 timer 作为「是否处于执行中」的锁。触发时若空闲,延迟 wait 后执行并上锁;执行完释放锁。这样停手后仍会把最后一次补执行一次。
- 优点:保证 trailing 执行。
- 缺点:首次不是立即执行,而是延迟
wait。
2.3 结合版(leading + trailing)
时间戳 + 定时器合体:既保证首次立即执行,又保证最后一次停止触发后一定收尾。这是 lodash 的行为。
- 时间复杂度:每次触发
O(1)。
3. TypeScript 实现
3.1 时间戳版
1 | function throttleTimestamp<T extends (...args: any[]) => void>( |
3.2 定时器版
1 | function throttleTimer<T extends (...args: any[]) => void>( |
3.3 结合版(leading + trailing,推荐)
1 | function throttle<T extends (...args: any[]) => void>(fn: T, wait: number) { |
4. 面试延伸
- 节流 vs 防抖 一句话:节流是「匀速放行」,防抖是「攒一波再放」;打字联想用防抖,滚动/射击用节流。
requestAnimationFrame节流:滚动/resize 类视觉更新用rAF对齐浏览器帧率比setTimeout更流畅省电。passive: true:给scroll/touch监听加被动选项,配合节流进一步消除卡顿,全栈性能考点。- lodash
throttle本质就是debounce(fn, wait, { leading: true, trailing: true })的特例,理解这层关系能秒杀追问。
难度:中等 | 前端手写题必考 | 性能优化基本功