312 lines
8.3 KiB
TypeScript
312 lines
8.3 KiB
TypeScript
/**
|
||
* 节流函数
|
||
* @param func 要节流的函数
|
||
* @param wait 等待时间(毫秒)
|
||
* @param options 选项
|
||
*
|
||
* 解释
|
||
* leading 表示首次调用时立即执行函数
|
||
* trailing: true 表示最后一次调用后等待 wait 毫秒后执行函数
|
||
*
|
||
* 三种可能
|
||
* leading: true, trailing: true 表示首次调用时立即执行函数,最后一次调用后等待 wait 毫秒后补一次执行函数
|
||
* 第一次触发(remaining大于wait)->(if分支)立即执行
|
||
* 期间再次触发(remaining小于等于wait)->(else if分支)触发定时器,到时间补一次
|
||
*
|
||
* leading: true, trailing: false 表示首次调用时立即执行函数,最后一次调用后不等待 wait 毫秒执行函数
|
||
* 第一次触发(remaining大于wait)->(if分支)立即执行
|
||
* 期间再次触发(remaining小于等于wait)->不管
|
||
*
|
||
* leading: false, trailing: true 表示首次调用时不立即执行函数,最后一次调用后等待 wait 毫秒后执行函数
|
||
* 第一次触发(remaining等于wait)->(else if分支)触发定时器,到时间执行函数
|
||
* 期间再次触发->(else if分支)不断重置定时器,但remaining会不断减少
|
||
*/
|
||
export function throttle<T extends (...args: unknown[]) => unknown>(
|
||
func: T,
|
||
wait: number,
|
||
options: { leading?: boolean; trailing?: boolean } = {}
|
||
): (...args: Parameters<T>) => void {
|
||
let timeout: ReturnType<typeof setTimeout> | null = null;
|
||
let previous = 0;
|
||
|
||
const { leading = true, trailing = true } = options;
|
||
|
||
return function(this: unknown, ...args: Parameters<T>) {
|
||
const now = Date.now();
|
||
|
||
if (!previous && !leading) previous = now;
|
||
|
||
const remaining = wait - (now - previous);
|
||
|
||
if (remaining <= 0 || remaining > wait) {
|
||
if (timeout) {
|
||
clearTimeout(timeout);
|
||
timeout = null;
|
||
}
|
||
previous = now;
|
||
func.apply(this, args);
|
||
} else if (!timeout && trailing) {
|
||
timeout = setTimeout(() => {
|
||
previous = leading ? Date.now() : 0;
|
||
timeout = null;
|
||
func.apply(this, args);
|
||
}, remaining);
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 深拷贝函数
|
||
* @param obj 要拷贝的对象
|
||
* @returns 深拷贝后的对象
|
||
*/
|
||
export const deepClone = <T>(obj: T): T => {
|
||
if (obj === null || typeof obj !== 'object') {
|
||
return obj;
|
||
}
|
||
|
||
// 处理日期对象
|
||
if (obj instanceof Date) {
|
||
return new Date(obj.getTime()) as unknown as T;
|
||
}
|
||
|
||
// 上面是边界情况判断,下面才是真的深拷贝逻辑
|
||
// 处理数组
|
||
if (Array.isArray(obj)) {
|
||
return obj.map(item => deepClone(item)) as unknown as T;
|
||
}
|
||
|
||
// 处理普通对象
|
||
const clonedObj = {} as T;
|
||
for (const key in obj) {
|
||
// 最安全的一集
|
||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||
// 递归
|
||
clonedObj[key] = deepClone(obj[key]);
|
||
}
|
||
}
|
||
|
||
return clonedObj;
|
||
};
|
||
|
||
/**
|
||
* 深度比较两个值是否相等
|
||
* @param a 第一个值
|
||
* @param b 第二个值
|
||
* @returns 是否相等
|
||
*/
|
||
export function deepEqual(a: unknown, b: unknown): boolean {
|
||
if (a === b) return true;
|
||
|
||
if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {
|
||
return false;
|
||
}
|
||
|
||
const keysA = Object.keys(a as Record<string, unknown>);
|
||
const keysB = Object.keys(b as Record<string, unknown>);
|
||
|
||
if (keysA.length !== keysB.length) return false;
|
||
|
||
for (const key of keysA) {
|
||
// 递归,上面比值,下面先确认键一不一样
|
||
if (!keysB.includes(key) || !deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 从对象中选取指定的属性
|
||
* @param obj 源对象
|
||
* @param keys 要选取的属性键数组
|
||
* @returns 包含指定属性的新对象
|
||
* 对象或数组类型属性:拷贝引用
|
||
* 基本类型属性:直接赋值
|
||
*/
|
||
export function pick<T extends Record<string, unknown>, K extends keyof T>(
|
||
obj: T,
|
||
keys: K[]
|
||
): Pick<T, K> {
|
||
const result = {} as Pick<T, K>;
|
||
for (const key of keys) {
|
||
if (key in obj) {
|
||
result[key] = obj[key];
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 从对象中排除指定的属性
|
||
* @param obj 源对象
|
||
* @param keys 要排除的属性键数组
|
||
* @returns 排除指定属性后的新对象
|
||
*/
|
||
export function omit<T extends Record<string, unknown>, K extends keyof T>(
|
||
obj: T,
|
||
keys: K[]
|
||
): Omit<T, K> {
|
||
const result = { ...obj } as T;
|
||
for (const key of keys) {
|
||
delete result[key];
|
||
}
|
||
return result as Omit<T, K>;
|
||
}
|
||
|
||
/**
|
||
* 合并多个对象
|
||
* @param objects 要合并的对象数组
|
||
* @returns 合并后的新对象
|
||
*/
|
||
export function merge<T extends Record<string, unknown>>(...objects: Partial<T>[]): T {
|
||
const result = {} as T;
|
||
|
||
for (const obj of objects) {
|
||
if (obj && typeof obj === 'object') {
|
||
// 浅拷贝
|
||
Object.assign(result, obj);
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 将对象转换为查询字符串(但是没用到)
|
||
* @param obj 要转换的对象
|
||
* @returns 查询字符串
|
||
*/
|
||
export const toQueryString = (obj: Record<string, unknown>): string => {
|
||
const params = new URLSearchParams();
|
||
|
||
for (const key in obj) {
|
||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||
const value = obj[key];
|
||
if (value !== null && value !== undefined) {
|
||
params.append(key, String(value));
|
||
}
|
||
}
|
||
}
|
||
|
||
return params.toString();
|
||
};
|
||
|
||
/**
|
||
* 将查询字符串转换为对象
|
||
* @param queryString 查询字符串
|
||
* @returns 转换后的对象
|
||
*/
|
||
export const fromQueryString = (queryString: string): Record<string, string> => {
|
||
const params = new URLSearchParams(queryString);
|
||
const result: Record<string, string> = {};
|
||
|
||
for (const [key, value] of params.entries()) {
|
||
result[key] = value;
|
||
}
|
||
|
||
return result;
|
||
};
|
||
|
||
/**
|
||
* 数组去重
|
||
* @param array 要去重的数组
|
||
* @param keyFn 可选的键函数,用于复杂对象去重
|
||
* @returns 去重后的数组
|
||
*/
|
||
export const unique = <T, K = unknown>(
|
||
array: T[],
|
||
keyFn?: (item: T) => K
|
||
): T[] => {
|
||
if (!keyFn) {
|
||
// 只适用于简单数组(也是蛮神奇的语法)
|
||
return [...new Set(array)];
|
||
}
|
||
|
||
const seen = new Set<K>();
|
||
return array.filter(item => {
|
||
const key = keyFn(item);
|
||
if (seen.has(key)) {
|
||
// 已存在,返回false过滤该元素
|
||
return false;
|
||
}
|
||
seen.add(key);
|
||
// 返回true保留该元素
|
||
return true;
|
||
});
|
||
};
|
||
|
||
/**
|
||
* 数组分组
|
||
* @param array 要分组的数组
|
||
* @param keyFn 分组键函数(箭头函数实现)
|
||
* @returns 分组后的对象
|
||
*/
|
||
export const groupBy = <T, K extends string | number | symbol>(
|
||
array: T[],
|
||
keyFn: (item: T) => K
|
||
): Record<K, T[]> => {
|
||
return array.reduce((groups, item) => {
|
||
const key = keyFn(item);
|
||
if (!groups[key]) {
|
||
groups[key] = [];
|
||
}
|
||
groups[key].push(item);
|
||
return groups;
|
||
}, {} as Record<K, T[]>);
|
||
};
|
||
|
||
/**
|
||
* 数组排序
|
||
* @param array 要排序的数组
|
||
* @param compareFn 比较函数(箭头函数实现)
|
||
* @returns 排序后的新数组
|
||
*/
|
||
export const sortBy = <T>(
|
||
array: T[],
|
||
compareFn?: (a: T, b: T) => number
|
||
): T[] => {
|
||
return [...array].sort(compareFn);
|
||
};
|
||
|
||
/**
|
||
* 防抖函数
|
||
* @param func 要防抖的函数
|
||
* @param wait 等待时间(毫秒)
|
||
* @param immediate 是否立即执行
|
||
*
|
||
* 两种类型:
|
||
* 1. 立即执行:第一次触发立即执行,之后触发只重置等待时间
|
||
* 2. 非立即执行:触发后等待时间结束才执行,期间触发会重置等待时间
|
||
*
|
||
* immediate=true 立即执行
|
||
* 第一次触发(callNow为true)->设定定时器(时间到了消除定时器),并立即执行
|
||
* 期间多次触发->不断重置定时器的等待时间
|
||
*
|
||
* immediate=false 非立即执行
|
||
* 第一次触发(callNow为false)->设定定时器(时间到了调用函数)
|
||
* 期间多次触发->不断重置定时器的等待时间
|
||
*/
|
||
export function debounce<T extends (...args: unknown[]) => unknown>(
|
||
func: T,
|
||
wait: number,
|
||
immediate = false
|
||
): (...args: Parameters<T>) => void {
|
||
let timeout: ReturnType<typeof setTimeout> | null = null;
|
||
|
||
return function(this: unknown, ...args: Parameters<T>) {
|
||
const later = () => {
|
||
timeout = null;
|
||
if (!immediate) func.apply(this, args);
|
||
};
|
||
|
||
const callNow = immediate && !timeout;
|
||
|
||
if (timeout) clearTimeout(timeout);
|
||
timeout = setTimeout(later, wait);
|
||
|
||
if (callNow) func.apply(this, args);
|
||
};
|
||
}
|