节流可限制高频事件单位时间内最多执行一次回调,避免重复计算和性能浪费;其核心是固定间隔内仅执行首次触发,适合需稳定节奏的场景如搜索请求或滚动更新。

高频触发的自定义事件(比如 input、scroll、mousemove 或自定义的 data-updated)直接监听并执行逻辑,容易造成重复计算、DOM 频繁操作或接口反复调用。用节流(throttle)能限制单位时间内最多执行一次回调,既保留响应性,又避免性能浪费。
节流的核心逻辑:固定间隔内只执行首次触发
节流不是“等停了再执行”,而是“在设定的时间窗口里,只让第一次触发生效,后续触发被忽略,直到窗口重置”。适合需要稳定节奏、不追求实时但需保障最低响应频率的场景(如搜索框输入后 300ms 发请求、滚动中每 100ms 更新一次吸顶状态)。
你可以手写一个轻量节流函数:
function throttle(fn, delay) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= delay) {
fn.apply(this, args);
last = now;
}
};
}使用时包装你的事件处理函数:
const handleUpdate = throttle(() => {
console.log('数据已更新,执行渲染或上报');
}, 300);
<p>// 绑定到自定义事件
document.addEventListener('data-updated', handleUpdate);
绑定自定义事件时注意事件对象的上下文与参数传递
原生 dispatchEvent 触发的自定义事件,其事件对象(event.detail)会作为参数传给监听器。节流函数需确保能透传这些参数:
- 上面的手写节流已用
...args和fn.apply(this, args)正确保留了this和所有入参 - 若监听的是
CustomEvent,通常关注event.detail,所以处理函数应写成:throttle((e) => { const data = e.detail; /* 处理 data */ }, 300) - 避免写成
throttle(() => { ... }, 300)后再手动取event——节流包装后,原始事件对象就是第一个参数
区分节流和防抖,别用错场景
节流适用于“持续发生、需定期响应”的场景;防抖(debounce)适用于“等待停止后再响应”的场景(如搜索联想)。例如:
- 用户拖拽调整面板宽度 → 用节流(每 100ms 同步一次尺寸)
- 输入框打字搜商品 → 更适合防抖(等用户停 300ms 再请求)
- 自定义事件
chart-resized被频繁派发 → 节流更合理 - 自定义事件
form-dirty表示表单有修改 → 一般无需节流,除非是高频输入导致连续触发
进阶:用 AbortController 配合节流避免过期执行
如果节流回调里有异步操作(如 fetch),而下一次节流窗口到来前上一个请求还没结束,可能造成竞态问题。可结合 AbortController 中断旧请求:
let controller = null;
<p>const throttledFetch = throttle((query) => {
if (controller) controller.abort();
controller = new AbortController();</p><p>fetch('/api/search', {
signal: controller.signal,
body: JSON.stringify({ q: query })
}).then(r => r.json()).then(data => {
// 渲染结果
}).catch(err => {
if (err.name !== 'AbortError') console.error(err);
});
}, 400);这样每次新节流周期开始,都会取消上一轮未完成的请求,保证最终生效的是最新一次触发的结果。


















