/** * 节流函数 * @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 unknown>( func: T, wait: number, options: { leading?: boolean; trailing?: boolean } = {} ): (...args: Parameters) => void { let timeout: ReturnType | null = null; let previous = 0; const { leading = true, trailing = true } = options; return function(this: unknown, ...args: Parameters) { 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 = (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); const keysB = Object.keys(b as Record); if (keysA.length !== keysB.length) return false; for (const key of keysA) { // 递归,上面比值,下面先确认键一不一样 if (!keysB.includes(key) || !deepEqual((a as Record)[key], (b as Record)[key])) { return false; } } return true; } /** * 从对象中选取指定的属性 * @param obj 源对象 * @param keys 要选取的属性键数组 * @returns 包含指定属性的新对象 * 对象或数组类型属性:拷贝引用 * 基本类型属性:直接赋值 */ export function pick, K extends keyof T>( obj: T, keys: K[] ): Pick { const result = {} as Pick; for (const key of keys) { if (key in obj) { result[key] = obj[key]; } } return result; } /** * 从对象中排除指定的属性 * @param obj 源对象 * @param keys 要排除的属性键数组 * @returns 排除指定属性后的新对象 */ export function omit, K extends keyof T>( obj: T, keys: K[] ): Omit { const result = { ...obj } as T; for (const key of keys) { delete result[key]; } return result as Omit; } /** * 合并多个对象 * @param objects 要合并的对象数组 * @returns 合并后的新对象 */ export function merge>(...objects: Partial[]): 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 => { 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 => { const params = new URLSearchParams(queryString); const result: Record = {}; for (const [key, value] of params.entries()) { result[key] = value; } return result; }; /** * 数组去重 * @param array 要去重的数组 * @param keyFn 可选的键函数,用于复杂对象去重 * @returns 去重后的数组 */ export const unique = ( array: T[], keyFn?: (item: T) => K ): T[] => { if (!keyFn) { // 只适用于简单数组(也是蛮神奇的语法) return [...new Set(array)]; } const seen = new Set(); 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 = ( array: T[], keyFn: (item: T) => K ): Record => { return array.reduce((groups, item) => { const key = keyFn(item); if (!groups[key]) { groups[key] = []; } groups[key].push(item); return groups; }, {} as Record); }; /** * 数组排序 * @param array 要排序的数组 * @param compareFn 比较函数(箭头函数实现) * @returns 排序后的新数组 */ export const sortBy = ( 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 unknown>( func: T, wait: number, immediate = false ): (...args: Parameters) => void { let timeout: ReturnType | null = null; return function(this: unknown, ...args: Parameters) { 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); }; }