## 场景与目标
- 高频事件(`scroll`、`resize`、`input`、`mousemove`)在无约束处理时会大量触发,导致主线程阻塞与掉帧。
- 通过节流(throttle)与防抖(debounce)削峰与合并,保障用户体验与性能稳定性。
## 概念与差异
- 防抖:在触发结束后延迟执行,连续触发只保留最后一次。适用于输入框联想、窗口尺寸调整完成后的计算。
- 节流:在固定时间窗口内最多执行一次,平滑控制调用频率。适用于滚动定位、滚动加载、鼠标移动采样。
## 已验证参数建议
- 滚动监听:节流 `100–200ms`;页面高度大、复杂布局取 `150–200ms`,轻量场景可取 `100–120ms`。
- 输入框联想:防抖 `250–400ms`;中文输入法与移动端建议 `350–400ms`,英文桌面可取 `250–300ms`。
- 动画相关(如滚动同步高亮):使用 `requestAnimationFrame` 搭配轻量节流 `~60–120ms`;或在 RAF 内做采样,避免与帧调度冲突。
## 实现代码(含边界处理)
// utils/throttle-debounce.js
export function debounce(fn, wait = 300, options = { leading: false, trailing: true }) {
let timer = null;
let lastArgs = null;
let lastThis = null;
let invoked = false;
return function debounced(...args) {
lastArgs = args;
lastThis = this;
if (options.leading && !invoked) {
fn.apply(lastThis, lastArgs);
invoked = true;
}
clearTimeout(timer);
timer = setTimeout(() => {
if (options.trailing && (!options.leading || invoked)) {
fn.apply(lastThis, lastArgs);
}
timer = null;
invoked = false;
}, wait);
};
}
export function throttle(fn, wait = 150, options = { leading: true, trailing: true }) {
let lastCall = 0;
let timer = null;
let lastArgs = null;
let lastThis = null;
const invoke = () => {
lastCall = Date.now();
fn.apply(lastThis, lastArgs);
lastArgs = lastThis = null;
};
return function throttled(...args) {
const now = Date.now();
lastArgs = args;
lastThis = this;
if (!lastCall && options.leading === false) {
lastCall = now;
}
const remaining = wait - (now - lastCall);
if (remaining <= 0 || remaining > wait) {
if (timer) { clearTimeout(timer); timer = null; }
invoke();
} else if (options.trailing && !timer) {
timer = setTimeout(() => {
invoke();
timer = null;
}, remaining);
}
};
}
## 使用示例与参数验证
- 滚动监听(列表定位/懒加载):
import { throttle } from './utils/throttle-debounce.js';
const onScroll = throttle(() => {
// 计算当前视窗区块索引或触发加载
// 已验证:在复杂页面下 150–200ms 可显著降低重排与样式计算成本
}, 180);
window.addEventListener('scroll', onScroll, { passive: true });
- 输入框联想(API 请求合并):
import { debounce } from './utils/throttle-debounce.js';
const search = debounce(async (q) => {
if (!q || q.length < 2) return;
const res = await fetch('/api/search?q=' + encodeURIComponent(q));
// 渲染联想结果
}, 350);
document.querySelector('#search').addEventListener('input', (e) => {
search(e.target.value);
});
- 动画相关(与 RAF 协作):
let ticking = false;
function onScrollRAF() {
if (!ticking) {
requestAnimationFrame(() => {
// 轻量计算,如更新指示器位置
ticking = false;
});
ticking = true;
}
}
window.addEventListener('scroll', onScrollRAF, { passive: true });
## 注意事项与测试要点
- `passive: true` 用于滚动监听,避免阻塞浏览器优化路径。
- 结合 `IntersectionObserver` 进行可见性判断,可进一步减少滚动计算次数。
- 在移动端与低端设备上,适当提高 `wait` 值以避免掉帧;在桌面高性能设备上可下调以提升响应性。
- 建议以 `PerformanceObserver` 或 `web-vitals` 采样验证参数,确保 CLS/LCP/FID 不受负面影响。
## 总结
- 在不同交互场景下选择合适的节流/防抖参数,能在保障响应性的同时显著降低主线程负载。
- 上述实现考虑了前后沿触发、尾沿补偿与会话体验,参数区间已通过实际采样验证。

发表评论 取消回复