您现在的位置是:网站首页 > 防抖(Debounce)与节流(Throttle)模式的事件处理文章详情
防抖(Debounce)与节流(Throttle)模式的事件处理
陈川
【
JavaScript
】
40084人已围观
5388字
防抖和节流是两种优化高频事件处理的技术,能有效控制事件触发的频率,避免性能问题。它们在前端开发中应用广泛,尤其在滚动、窗口调整、输入框实时搜索等场景下。
防抖(Debounce)模式
防抖的核心思想是:在事件被触发后,延迟执行回调函数。如果在延迟期间事件再次被触发,则重新计时。只有当事件停止触发一段时间后,回调才会真正执行。
基本实现
function debounce(func, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
应用场景
- 搜索框输入:用户连续输入时不立即请求,只在停止输入300ms后发起搜索
- 窗口大小调整:避免resize事件频繁触发布局计算
- 表单验证:不在每次输入时验证,只在用户停止输入后验证
立即执行版本
有时我们需要第一次触发立即执行,后续触发才防抖:
function debounceImmediate(func, delay, immediate) {
let timer;
return function(...args) {
const context = this;
const callNow = immediate && !timer;
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
if (!immediate) func.apply(context, args);
}, delay);
if (callNow) func.apply(context, args);
};
}
节流(Throttle)模式
节流的核心思想是:在一定时间间隔内,只执行一次回调函数。无论事件触发多么频繁,都会按照固定频率执行。
基本实现
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
应用场景
- 滚动事件:滚动时每100ms检查一次位置,而不是每次滚动都触发
- 鼠标移动:拖拽元素时限制位置更新的频率
- 动画渲染:保证动画帧率稳定,避免过度渲染
时间戳版本
另一种实现方式是使用时间戳比较:
function throttleTimestamp(func, limit) {
let lastTime;
return function(...args) {
const now = Date.now();
if (!lastTime || now - lastTime >= limit) {
func.apply(this, args);
lastTime = now;
}
};
}
防抖与节流的比较
特性 | 防抖 | 节流 |
---|---|---|
执行时机 | 事件停止后执行 | 固定间隔执行 |
适用场景 | 关注最终状态 | 关注过程状态 |
连续触发效果 | 只执行最后一次 | 均匀执行 |
典型应用 | 搜索建议、窗口resize | 滚动加载、游戏按键 |
高级应用与变体
组合使用
某些场景需要先防抖再节流:
function debounceThrottle(func, debounceTime, throttleTime) {
let lastCall = 0;
let timer;
return function(...args) {
const now = Date.now();
clearTimeout(timer);
if (now - lastCall >= throttleTime) {
lastCall = now;
func.apply(this, args);
} else {
timer = setTimeout(() => {
lastCall = now;
func.apply(this, args);
}, debounceTime);
}
};
}
requestAnimationFrame节流
对于动画场景,可以使用rAF实现更流畅的节流:
function throttleRAF(func) {
let isRunning;
return function(...args) {
if (!isRunning) {
isRunning = true;
requestAnimationFrame(() => {
func.apply(this, args);
isRunning = false;
});
}
};
}
实际项目中的考量
- 取消机制:实现可取消的防抖/节流函数
- 返回值处理:处理带返回值的函数调用
- leading/trailing:控制是否在时间间隔开始或结束时触发
- 最大等待时间:设置防抖的最大延迟上限
function advancedDebounce(func, wait, options = {}) {
let timeout, context, args, result;
let previous = 0;
const later = function() {
previous = options.leading === false ? 0 : Date.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
const debounced = function() {
const now = Date.now();
if (!previous && options.leading === false) previous = now;
const remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
debounced.cancel = function() {
clearTimeout(timeout);
timeout = context = args = null;
};
return debounced;
}
在现代框架中的应用
React中的实现
import { useCallback, useEffect, useRef } from 'react';
function useDebounce(callback, delay) {
const timeoutRef = useRef();
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
return useCallback((...args) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
callback(...args);
}, delay);
}, [callback, delay]);
}
Vue中的实现
import { customRef } from 'vue';
export function useDebouncedRef(value, delay = 200) {
let timeout;
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newValue) {
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newValue;
trigger();
}, delay);
}
};
});
}
性能优化实践
- 合理设置延迟时间:根据场景选择适当的值(搜索建议200-300ms,滚动50-100ms)
- 避免内存泄漏:在组件卸载时清除定时器
- 批量处理事件:对高频事件进行批量处理而非单个处理
- 使用Passive事件监听器:特别是touch/wheel事件
// 优化滚动性能
window.addEventListener('scroll', throttle(handleScroll, 100), { passive: true });
常见误区与陷阱
- 过度使用防抖:可能导致界面响应迟钝
- 忽略this绑定:在回调函数中丢失正确的this指向
- 定时器未清除:引起内存泄漏
- 忽略返回值:防抖/节流后的函数返回值可能不符合预期
- 时间间隔设置不当:太短达不到效果,太长影响用户体验