Files
knowai/utils/data.ts
2025-11-23 22:26:39 +08:00

312 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 节流函数
* @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);
};
}