export interface CancelableFunction {
(...args: A): void;
cancel: () => void;
}
/**
* @description 创建可取消的防抖函数。
* @param {(...args: A) => void} fn 需要防抖的函数。
* @param {number} delay 防抖延迟毫秒数。
* @returns {CancelableFunction} 可取消的防抖函数。
*/
export function createDebounced(
fn: (...args: A) => void,
delay: number,
): CancelableFunction {
let timer: ReturnType | null = null;
const debounced = (...args: A) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
fn(...args);
}, delay);
};
debounced.cancel = () => {
if (timer) {
clearTimeout(timer);
timer = null;
}
};
return debounced;
}
/**
* @description 创建可取消的节流函数。
* @param {(...args: A) => void} fn 需要节流的函数。
* @param {number} delay 节流延迟毫秒数。
* @returns {CancelableFunction} 可取消的节流函数。
*/
export function createThrottled(
fn: (...args: A) => void,
delay: number,
): CancelableFunction {
let lastCalledAt = 0;
let timer: ReturnType | null = null;
let lastArgs: A | null = null;
const throttled = (...args: A) => {
const now = Date.now();
const remaining = delay - (now - lastCalledAt);
lastArgs = args;
if (remaining <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
lastCalledAt = now;
fn(...args);
lastArgs = null;
return;
}
if (!timer) {
timer = setTimeout(() => {
timer = null;
lastCalledAt = Date.now();
if (lastArgs) {
fn(...lastArgs);
lastArgs = null;
}
}, remaining);
}
};
throttled.cancel = () => {
if (timer) {
clearTimeout(timer);
timer = null;
}
lastArgs = null;
};
return throttled;
}
/**
* @desc 防抖装饰器
* @param {number} delay 防抖延迟毫秒数。
* @param {boolean} immediate 是否在首次触发时立即执行。
* @returns {MethodDecorator}
*/
export function Debounce(delay: number, immediate: boolean = false) {
return function (
_target: any,
_propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<(...args: A) => R>,
) {
let timer: ReturnType | null = null;
const originalMethod = descriptor.value!;
let allowCallNow: boolean = immediate;
descriptor.value = function (this: ThisParameterType<(...args: A) => R>, ...args: A) {
const context = this;
if (timer) {
clearTimeout(timer);
timer = null;
}
if (immediate && allowCallNow) {
// 即时模式下执行了函数就暂时禁止执行
allowCallNow = false;
return originalMethod.apply(context, args);
}
timer = setTimeout(() => {
timer = null;
// 延迟结束后重置执行标记
// allowCallNow = immediate;
return originalMethod.apply(context, args);
}, delay);
};
return descriptor;
};
}
/**
* @description 判断传入值是否为 HTMLElement。
* @param {unknown} value 需要判断的值。
* @returns {value is HTMLElement} 当值为 HTMLElement 时返回 true。
*/
export function isHTMLElement(value: unknown): value is HTMLElement {
return value instanceof HTMLElement;
}
/**
* @desc 节流装饰器
* @param {number} delay 节流延迟毫秒数。
* @param {boolean} leading 是否在首次触发时执行。
* @returns {MethodDecorator}
*/
export function Throttle(delay: number, leading: boolean = true) {
return function (
_target: any,
_propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<(...args: A) => R>,
) {
let previous = 0;
let timer: ReturnType | null = null;
const originalMethod = descriptor.value!;
descriptor.value = function (this: ThisParameterType<(...args: A) => R>, ...args: A) {
const now = Date.now();
if (leading && !previous) {
const result = originalMethod.apply(this, args);
previous = now;
return result;
} else if (now - previous > delay) {
clearTimeout(timer as ReturnType);
timer = setTimeout(() => {
const result = originalMethod.apply(this, args);
previous = Date.now();
return result;
}, delay);
}
};
return descriptor;
};
}