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