概念
防抖是一种优化技术,用于减少高频率事件(如输入框输入、滚动事件等)触发的执行次数。
它会延迟执行目标函数,直到事件停止触发的一段时间后才执行。
如果在延迟时间内再次触发事件,则重新计时。
基础版
/**
* 防抖函数 (Debounce)
*
* @param func - 目标函数,表示需要被防抖处理的函数
* @param delay - 延迟时间(毫秒),表示事件触发后多长时间执行目标函数
* @returns 返回一个防抖后的函数
*/
function debounce<T extends (...args: any[]) => void>(func: T, delay: number): (...args: Parameters<T>) => void {
// 定时器,用于记录延迟的时间
let timer: NodeJS.Timeout | null = null;
// 返回一个新的函数,用于包裹目标函数
return function (...args: Parameters<T>) {
// 如果定时器已经存在,清除当前定时器(重置计时)
if (timer) {
clearTimeout(timer);
}
// 设置一个新的定时器,在延迟时间到达后执行目标函数
timer = setTimeout(() => {
// 调用传入的目标函数,传递参数
func(...args);
}, delay);
};
}
export default debounce;
进阶版
此版本的防抖函数支持以下功能:
- 保持
this
指向调用者。 - 支持立即执行:在第一次调用时立即执行函数,而不是等待延迟。
/**
* 防抖函数 (Debounce)
*
* @param func - 目标函数,表示需要被防抖处理的函数
* @param delay - 延迟时间(毫秒),表示事件触发后多长时间执行目标函数
* @param immediate - 是否在第一次调用时立即执行
* @returns 返回一个防抖后的函数
*/
function debounce<T extends (...args: any[]) => void>(
func: T,
delay: number,
immediate: boolean = false
): (...args: Parameters<T>) => void {
// 定时器,用于记录延迟的时间
let timer: NodeJS.Timeout | null = null;
// 返回一个新的函数,用于包裹目标函数
return function (this: any, ...args: Parameters<T>) {
// 如果设置了立即执行
const callNow = immediate && !timer;
// 如果定时器已经存在,清除当前定时器(重置计时)
if (timer) {
clearTimeout(timer);
}
// 设置一个新的定时器,在延迟时间到达后执行目标函数
timer = setTimeout(() => {
timer = null; // 清空定时器标记
if (!immediate) {
func.apply(this, args); // 延迟执行目标函数
}
}, delay);
// 如果是立即执行,则立即调用目标函数
if (callNow) {
func.apply(this, args);
}
};
}
export default debounce;
完整版
此版本的防抖函数支持以下功能:
- 保持
this
指向调用者。 - 支持立即执行:在第一次调用时立即执行函数,而不是等待延迟。
- 支持取消功能:可以通过
cancel
方法取消防抖中的目标函数执行。 - 支持强制执行:通过
flush
方法立即执行目标函数。 - 支持最大等待时间:即使持续触发事件,目标函数也会在最大等待时间到达时执行。
/**
* 防抖函数 (Debounce)
*
* @param func - 目标函数,表示需要被防抖处理的函数
* @param delay - 延迟时间(毫秒),表示事件触发后多长时间执行目标函数
* @param options - 配置选项
* - immediate: 是否在第一次调用时立即执行,默认为 false
* - maxWait: 最大等待时间(毫秒),即使持续触发事件,也会在该时间后执行目标函数
* @returns 返回一个防抖后的函数,并附带 `cancel` 和 `flush` 方法
*/
function debounce<T extends (...args: any[]) => any>(
func: T,
delay: number,
options: { immediate?: boolean; maxWait?: number } = {}
): {
(this: any, ...args: Parameters<T>): void;
cancel: () => void;
flush: () => void;
} {
const { immediate = false, maxWait } = options;
let timer: NodeJS.Timeout | null = null; // 定时器,用于延迟执行
let lastCallTime: number | null = null; // 上一次调用的时间
let lastInvokeTime: number | null = null; // 上一次实际执行目标函数的时间
let result: any; // 存储目标函数的返回值
// 防抖函数主体
function debounced(this: any, ...args: Parameters<T>): void {
const now = Date.now();
const isInvoking = shouldInvoke(now);
// 如果设置了立即执行并且条件满足
if (isInvoking) {
if (!lastInvokeTime || immediate) {
// 立即执行目标函数
result = invokeFunc.call(this, now, args);
}
}
// 如果定时器存在,清除当前定时器(重置计时)
if (timer) {
clearTimeout(timer);
}
// 设置最大等待时间的逻辑
if (!lastCallTime) {
lastCallTime = now;
}
// 如果最大等待时间已到,强制执行目标函数
if (maxWait && now - lastCallTime >= maxWait) {
result = invokeFunc.call(this, now, args);
}
// 设置新的定时器
timer = setTimeout(() => {
timer = null;
if (!immediate) {
result = invokeFunc.call(this, now, args);
}
}, delay);
}
// 判断是否应该立即执行目标函数
function shouldInvoke(time: number): boolean {
if (!lastInvokeTime) return immediate;
return maxWait ? time - lastInvokeTime >= maxWait : false;
}
// 实际调用目标函数
function invokeFunc(this: any, time: number, args: Parameters<T>) {
lastInvokeTime = time;
lastCallTime = null;
return func.apply(this, args);
}
// 取消防抖功能
debounced.cancel = function () {
if (timer) {
clearTimeout(timer);
timer = null;
}
lastCallTime = null;
lastInvokeTime = null;
};
// 强制执行目标函数
debounced.flush = function () {
if (timer) {
clearTimeout(timer);
timer = null;
}
if (lastCallTime) {
result = invokeFunc.call(this, Date.now(), []);
}
lastCallTime = null;
lastInvokeTime = null;
return result;
};
return debounced;
}
export default debounce;