Files
knowai/utils/data.ts
tobegold574 6a81b7bb13
Some checks reported errors
continuous-integration/drone/push Build was killed
feat(image): 新建 knowai-core:1.0.0 镜像并完成推送
- 搭建 api、auth、utils 等逻辑模块
- 通过 tsc、eslint、vitest 测试验证

BREAKING CHANGE: 新镜像分支
2025-11-10 20:20:25 +08:00

273 lines
6.1 KiB
TypeScript

/**
* 节流函数
* @param func 要节流的函数
* @param wait 等待时间(毫秒)
* @param options 选项
*/
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)) {
return false;
}
seen.add(key);
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 是否立即执行
*/
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);
};
}