搜索框每输入一个字母就发一次请求,网络面板里一排 pending,后端日志也刷得飞快。用户停手后只想要最后一次结果,这时候该用防抖,不是节流,更不是把请求间隔写死。

js防抖到底在等什么

防抖的做法是每次触发都清掉上一个定时器,等用户安静了 wait 毫秒再执行。输入框常用 300 毫秒,按钮防连点可以短一些。它适合「只关心最后一次」的场景,比如搜索联想、窗口 resize 后重算布局。滚动加载这种需要固定频率的场景,用节流更合适。

一个能直接用的版本

function debounce(fn, wait = 300) { let timer; return function (...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), wait); }; }

里面保留 this 和参数,事件回调里才能拿到正确的元素和事件对象。需要首次立即执行,可以加一个 immediate 参数,在定时器为空时先调用一次。组件卸载或页面跳转前,记得 clearTimeout,避免回调去改已经销毁的节点。

搜索框还要处理竞态

防抖只能减少请求数量,不能保证返回顺序。输入「abc」后先发的请求可能后回来,把「abcd」的结果覆盖掉。用 AbortController 取消旧请求,或者给每次请求编号,只接受最新一次的结果。接口很慢时,300 毫秒的等待可能还不够,按实际输入速度调到 400 到 500 毫秒。

别把所有事件都套上防抖

输入校验、按钮提交这类需要即时反馈的操作,过度防抖会让界面显得迟钝。表单提交更适合在请求期间禁用按钮,而不是延迟执行。写工具函数时把 wait 做成参数,别在函数体里写死;多个页面共用时,放在单独的 utils 文件里,改一处就够。

验证时,打开开发者工具的 Network,勾选 Preserve log,快速输入五个字符,看请求是不是只发了一次。再把网络调成 Slow 3G,确认加载状态和取消逻辑没有打架。js防抖写起来只有几行,难的是边界:组件销毁、请求取消、this 指向,这几处都测过再上线。