02高频事件实战

真实项目里最常写的不是 click,而是 输入、滚动、缩放、提交、键盘。 这些事件「来得又快又密」,直接写业务逻辑会卡、会重复请求、会丢焦点。 本课给每个高频事件配上正确的节流 / 防抖 / 惯用法。

约 16 分钟 4 个动手实验 性能与体验

一、谁是「高频事件」

input / keydown

打字时每字符触发一次;搜索建议若每次都请求会打爆后端。

scroll

滚动一帧可触发几十次;直接改 DOM 会掉帧。

resize

拖窗口边缘连续触发;图表重排要合并。

mousemove / pointermove

拖拽、画笔、tooltip 跟随,必须节流到 rAF。

submit

按钮连点 → 提交两次;要禁用 + 防重。

touchstart / wheel

滚动性能敏感;监听加 passive: true。

两条万能策略: 防抖 debounce(等你停下来再做:搜索框)—— 节流 throttle(固定频率做:滚动显示进度条)。

二、防抖 vs 节流(一图说清)

防抖 debounce节流 throttle
行为 停止操作 N ms 后执行一次 每 N ms 最多执行一次
场景 搜索联想、自动保存、按钮防连点 滚动加载、进度条、拖拽、埋点
口诀 「你停了我再动」 「你一直动我也按节拍动」
在输入框狂打字:① 原始 input 每键都触发;② 防抖 400ms 只在停顿时触发;③ 节流 400ms 固定节拍触发

三、scroll / resize:用 rAF 合并帧

浏览器一帧约 16ms。scroll 事件远比帧率密,直接布局读写会触发 强制同步布局(layout thrashing)。 标准做法:事件里只「记下要更新」,真正改样式丢到 requestAnimationFrame。

scroll / wheel / touchstart 监听务必 { passive: true }(除非你真的要 preventDefault), 否则浏览器不敢放开合成滚动,列表会发涩。

四、表单:防连点 + 校验 + 提交

① 不防重时狂点提交会发出多次「请求」;② 打开防重后按钮禁用;③ submit 上 preventDefault,不要用 click

五、键盘:keydown 判断按键

用 e.key(语义:'Enter')而不是 e.keyCode(已废弃)。中文输入法组字阶段注意 e.isComposing。

六、常见问题 · 自问自答

Q

搜索框为什么请求打到服务器爆了?

A

每个字符都触发了 fetch。给 input 监听套防抖 300–500ms; 更稳的还可以用 AbortController 取消上一次未完成的请求,防止旧结果覆盖新结果(08 章讲过)。

Q

滚动时页面很卡、掉帧怎么办?

A

① 监听是否写了 {passive:true}; ② 回调里是否在「读布局」和「写布局」之间来回跳(offsetHeight 后立刻改 style); ③ 把写操作合并进 requestAnimationFrame; ④ 能用 IntersectionObserver 做懒加载就别用 scroll。

Q

按钮连点导致提交两次、扣两次库存?

A

前端:提交瞬间 disabled = true + loading 文案; 更关键的是后端幂等(订单号去重)。前端防重只是体验,不能当唯一安全措施。

Q

为什么 resize 里重新算布局很卡?

A

拖窗口时 resize 每帧都来。用节流 100–200ms 或 rAF 合并; 图表库一般自带 resizeObserver,优先用 ResizeObserver 观察容器,而不是 window.resize。

Q

防抖里的函数 this 为什么丢了?

A

自己手写 debounce 时要用 fn.apply(this, args) 保存调用时的 this, 或者直接用箭头函数包业务,this 取外层。lodash 的 _.debounce 已帮你处理。

七、对照速查

事件推荐写法一坑提醒
inputdebounce 300–500中文组字用 compositionend
scrollthrottle 或 rAF + passive勿频繁改布局
resizedebounce 150 或 ResizeObserver拖拽窗口会连发
submitpreventDefault + 禁用按钮不要写在 click
keydowne.key / e.metaKey拦 Ctrl+S 要 preventDefault
wheel/touch{passive:true}要拦截滚动才非 passive

八、本章小结

  • 高频事件必须合并频率:防抖「停下来再做」、节流「按节拍做」。
  • scroll / 触摸:加 passive: true;改样式进 requestAnimationFrame。
  • 表单用 submit + preventDefault,按钮防重 + 后端幂等。
  • 键盘用 e.key,快捷键记得拦默认行为。
动手练习: ① 实现「输入即搜索」(debounce + AbortController); ② 阅读进度条(scroll + rAF + passive); ③ 提交按钮防连点,并在 Console 证明只发出一次。