From c5853847aefc96d9b3ad6408cb392a4e6212ce48 Mon Sep 17 00:00:00 2001 From: tobegold574 <2386340403@qq.com> Date: Sun, 23 Nov 2025 22:26:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(DOC):=20=E5=AE=8C=E6=95=B4=E5=9C=B0?= =?UTF-8?q?=E5=AE=A1=E6=9F=A5=E4=BA=86=E4=BB=A3=E7=A0=81=EF=BC=8C=E6=9B=B4?= =?UTF-8?q?=E5=AE=8C=E6=95=B4=E7=9A=84=E6=B3=A8=E9=87=8A=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 无代码修改 --- api/client.ts | 26 ++++++++++------ api/errors.ts | 4 +-- api/factory.ts | 3 +- api/modules/chat.ts | 4 +-- api/modules/index.ts | 2 +- api/modules/user.ts | 7 ----- api/types.ts | 1 + auth/errors.ts | 1 + auth/event-manager.ts | 1 + auth/session-manager.ts | 1 + auth/types.ts | 3 +- package.json | 11 ++++--- test/README.md | 67 ++++------------------------------------- test/setup.ts | 10 +++--- tsconfig.json | 3 +- types/chat/api.ts | 6 ++-- types/chat/base.ts | 2 ++ types/chat/enum.ts | 4 +-- types/model/base.ts | 1 + types/model/enum.ts | 1 + types/post/base.ts | 3 ++ types/post/enum.ts | 1 + types/user/api.ts | 2 ++ types/user/base.ts | 17 ++++++----- types/user/profile.ts | 2 ++ types/user/search.ts | 1 + utils/data.ts | 45 +++++++++++++++++++++++++-- 27 files changed, 119 insertions(+), 110 deletions(-) diff --git a/api/client.ts b/api/client.ts index 06338d6..36cbeef 100644 --- a/api/client.ts +++ b/api/client.ts @@ -3,7 +3,7 @@ import type { ApiClient } from './types'; import type { AxiosError, AxiosResponse, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios'; import { ApiError } from './errors'; // 直接导入ApiError -// 创建API客户端实例的工厂函数 +// 创建API客户端实例的工厂函数(直接用axios的requestConfig) const createApiClientInstance = (config?: Partial): ApiClient => { // 创建axios实例 const instance = axios.create({ @@ -22,8 +22,9 @@ const createApiClientInstance = (config?: Partial): ApiClien // 响应拦截器数组 const responseInterceptors: Array = []; - // 添加请求拦截器 + // 添加请求拦截器(函数声明,非函数实体,接收两个钩子参数) const addRequestInterceptor = ( + // InternalAxiosRequestConfig 是 axios 内部专门用来管理监听器的 onFulfilled?: (_config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise, onRejected?: (_error: unknown) => unknown ): number => { @@ -46,12 +47,14 @@ const createApiClientInstance = (config?: Partial): ApiClien const removeRequestInterceptor = (handler: number): void => { const index = requestInterceptors.indexOf(handler); if (index !== -1) { + // axios内部的移除逻辑 instance.interceptors.request.eject(handler); + // 封装管理的数据结构的移除(有点像LRU) requestInterceptors.splice(index, 1); } }; - // 移除响应拦截器 + // 移除响应拦截器(同上) const removeResponseInterceptor = (handler: number): void => { const index = responseInterceptors.indexOf(handler); if (index !== -1) { @@ -60,19 +63,20 @@ const createApiClientInstance = (config?: Partial): ApiClien } }; - // 设置默认配置 + // 设置默认配置(单例的) + // defaults是最底层请求配置 const setDefaults = (_config: Partial): void => { Object.assign(instance.defaults, _config); }; - // 更新基础URL - 专门用于Runtime层配置 + // 更新基础URL,比上面那个更细 const setBaseURL = (baseURL: string): void => { instance.defaults.baseURL = baseURL; }; // 创建新实例 - 用于跨域请求等特殊场景 const createInstance = (config?: Partial): ApiClient => { - // 创建新的axios实例 + // 创建新的axios实例(工厂模式+原型模式) const newInstance = axios.create({ ...instance.defaults, ...config @@ -83,6 +87,8 @@ const createApiClientInstance = (config?: Partial): ApiClien const newResponseInterceptors: Array = []; // 复制拦截器 + // 其实逻辑有很大问题,包括闭包问题、handler不会对应、双重断言等等 + // 应该不复制,而是重新添加 requestInterceptors.forEach(handler => { // 由于AxiosInterceptorManager类型定义中没有handlers属性, // 我们使用类型断言来访问内部属性 @@ -107,6 +113,7 @@ const createApiClientInstance = (config?: Partial): ApiClien } }); + // 同上 responseInterceptors.forEach(handler => { // 由于AxiosInterceptorManager类型定义中没有handlers属性, // 我们使用类型断言来访问内部属性 @@ -164,14 +171,14 @@ const createApiClientInstance = (config?: Partial): ApiClien newInstance.defaults.baseURL = baseURL; }, createInstance: (newConfig?: Partial): ApiClient => { - // 手动处理headers字段的类型转换 + // 手动处理headers字段的类型转换(这里newInstance实际上是原型) const { headers, ...otherDefaults } = newInstance.defaults; const configWithTypedHeaders: Partial = { ...otherDefaults, ...newConfig }; - // 只有当headers存在时才添加 + // 只有当headers存在时才添加(因为defaults和axiosRequestConfig在这里类型不一样) if (headers) { // 手动转换headers字段,确保类型安全 const convertedHeaders: Record = {}; @@ -275,5 +282,6 @@ const createApiClient = (config?: Partial) => { return createApiClientInstance(config); }; -// 导出API客户端和工厂函数 +// 导出API客户端单例和工厂函数 +// 其实单例没用,因为baseurl总归不一样的,但是怎么说呢,单例也只需要改一个baseurl就好了 export { apiClient, createApiClient }; diff --git a/api/errors.ts b/api/errors.ts index 6efffd0..9ce4038 100644 --- a/api/errors.ts +++ b/api/errors.ts @@ -13,7 +13,7 @@ export interface IApiError { } /** - * API错误类 + * API错误类(诶,就是要自定义) */ export class ApiError extends Error implements IApiError { public readonly code: number; @@ -27,7 +27,7 @@ export class ApiError extends Error implements IApiError { } /** - * 从Axios错误创建API错误 + * 从Axios错误创建API错误(就是为了简化) */ static fromAxiosError(error: AxiosError): ApiError { if (!error.response) { diff --git a/api/factory.ts b/api/factory.ts index 4a6ba16..a9ed835 100644 --- a/api/factory.ts +++ b/api/factory.ts @@ -6,7 +6,7 @@ import { chatApi } from './modules/chat'; import { modelApi } from './modules/model'; /** - * API 工厂函数,用于创建和管理 API 实例 + * API 工厂函数,用于创建和管理 API 实例(模块化封装) * 提供统一的 API 访问入口和配置管理 */ export const createApi = (config?: Partial) => { @@ -36,5 +36,6 @@ export const createApi = (config?: Partial) => { export const api = createApi(); // 向后兼容的导出 +// 单例还是输出了 export { apiClient } from './client'; export { postApi, userApi, chatApi, modelApi } from './modules'; diff --git a/api/modules/chat.ts b/api/modules/chat.ts index e5474c5..dfaf658 100644 --- a/api/modules/chat.ts +++ b/api/modules/chat.ts @@ -20,7 +20,7 @@ export const chatApi = (client: ApiClient) => ({ return client.post('/chat/sessions', data); }, - // 更新聊天会话 + // 更新聊天会话(纯充数那个) updateSession: ({ sessionId, ...data }: UpdateChatSessionRequest): Promise<{ session: ChatSession }> => { return client.put(`/chat/sessions/${sessionId}`, data); }, @@ -30,7 +30,7 @@ export const chatApi = (client: ApiClient) => ({ return client.post(`/chat/sessions/${data.sessionId}/messages`, data); }, - // 获取聊天会话列表 + // 获取聊天会话列表(不知道写了个什么玩意儿,转什么,应该不用参数的) getSessions: (params?: GetChatSessionsRequest): Promise => { const config: AxiosRequestConfig = params ? { params } : {}; return client.get('/chat/sessions', config); diff --git a/api/modules/index.ts b/api/modules/index.ts index a5e65b5..2968f7d 100644 --- a/api/modules/index.ts +++ b/api/modules/index.ts @@ -4,7 +4,7 @@ import { userApi as createUserApi } from './user'; import { chatApi as createChatApi } from './chat'; import { modelApi as createModelApi } from './model'; -// 导出工厂函数 +// 导出工厂函数(和实例同名导出是什么鬼啊),幸好不是从这里导出的 export { postApi as createPostApi, userApi as createUserApi, chatApi as createChatApi, modelApi as createModelApi }; // 向后兼容的默认实例 diff --git a/api/modules/user.ts b/api/modules/user.ts index 9739a2d..a399f1f 100644 --- a/api/modules/user.ts +++ b/api/modules/user.ts @@ -4,8 +4,6 @@ import type { LoginResponse, RegisterRequest, RegisterResponse, - RefreshTokenRequest, - RefreshTokenResponse, UserProfileUpdateRequest, UserProfileUpdateResponse, UserFollowRequest, @@ -28,11 +26,6 @@ export const userApi = (client: ApiClient) => ({ return client.post('/auth/register', data); }, - // 刷新令牌 - refreshToken: (data: RefreshTokenRequest): Promise => { - return client.post('/auth/refresh', data); - }, - // 获取用户档案 getProfile: (): Promise => { return client.get('/user/profile'); diff --git a/api/types.ts b/api/types.ts index eecf3a7..588b33e 100644 --- a/api/types.ts +++ b/api/types.ts @@ -27,6 +27,7 @@ export interface ApiClient { removeResponseInterceptor(handler: number): void; } +// 后面的除了test里用,core里其他地方根本没用上过,都用泛型unknown了,这是不对的 // API响应接口 export interface ApiResponse { success: boolean; diff --git a/auth/errors.ts b/auth/errors.ts index d42f5fe..4f038a3 100644 --- a/auth/errors.ts +++ b/auth/errors.ts @@ -11,6 +11,7 @@ export class AuthError extends Error { constructor(code: string, message: string, details?: unknown) { super(message); + // 这个得删,还有test文件里的也要删 this.name = 'AuthError'; this.code = code; this.details = details; diff --git a/auth/event-manager.ts b/auth/event-manager.ts index a7dc71e..70ffd68 100644 --- a/auth/event-manager.ts +++ b/auth/event-manager.ts @@ -19,6 +19,7 @@ export class AuthEventManager { this.listeners.set(event, []); } const eventListeners = this.listeners.get(event); + // 神人判断 if (eventListeners) { eventListeners.push(listener); } diff --git a/auth/session-manager.ts b/auth/session-manager.ts index 7fff07a..a9fa52a 100644 --- a/auth/session-manager.ts +++ b/auth/session-manager.ts @@ -64,6 +64,7 @@ export class DefaultSessionManager { */ clearCache(): void { this.currentUser = null; + // 神人判断,没用接口 // 清除存储适配器中的所有项(如果支持) if ('clear' in this.storage && typeof this.storage.clear === 'function') { this.storage.clear(); diff --git a/auth/types.ts b/auth/types.ts index 8b18f34..a8ae87b 100644 --- a/auth/types.ts +++ b/auth/types.ts @@ -13,7 +13,7 @@ import type { // 事件类型定义 export type AuthEventType = 'login' | 'logout' | 'register' | 'session_expired' | 'session_authenticated' | 'session_logout'; -// 事件监听器类型 +// 事件监听器类型(就是函数) export type AuthEventListener = (...args: unknown[]) => void; // 存储适配器接口 - 仅用于非敏感数据存储,不涉及session管理 @@ -77,6 +77,7 @@ export interface SessionManager { } // 扩展 Axios 请求配置以支持认证选项 +// 其实没必要,因为是服务器端处理过期 declare module 'axios' { interface AxiosRequestConfig { skipAuth?: boolean; diff --git a/package.json b/package.json index 5079549..480bc31 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,12 @@ "version": "1.0.0", "type": "module", "description": "负责准备视觉层会用到的逻辑函数、常用工具函数封装以及鉴权函数封装。负责维护统一接口。", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "files": [ - "dist" - ], + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, "scripts": { "type-check": "tsc --noemit", "build": "tsc --project tsconfig.json", diff --git a/test/README.md b/test/README.md index 8f19026..a48faa0 100644 --- a/test/README.md +++ b/test/README.md @@ -2,21 +2,6 @@ 本目录包含了对新认证架构的全面测试套件,验证SessionManager、AccessTokenManager、SessionAuthService、ExtendedApiClient和兼容性处理的功能和安全性。 -## 测试结构 - -``` -test/ -├── auth/ -│ ├── session-manager.test.ts # SessionManager测试 -│ ├── access-token-manager.test.ts # AccessTokenManager测试 -│ ├── session-auth-service.test.ts # SessionAuthService测试 -│ ├── compatibility.test.ts # 兼容性处理测试 -│ └── integration.test.ts # 集成测试 -├── api/ -│ └── extended-client.test.ts # ExtendedApiClient测试 -└── setup.ts # 测试环境设置 -``` - ## 测试覆盖范围 ### SessionManager测试 @@ -26,7 +11,7 @@ test/ - Session清除 - 用户角色检查 -### AccessTokenManager测试 +### AccessTokenManager测试(已废弃) - Token生成 - Token获取和缓存 - Token刷新 @@ -45,50 +30,17 @@ test/ - Token注入 - 错误处理 -### 兼容性处理测试 +### 兼容性处理测试(已废弃) - 认证模式切换 - Token到Session的迁移 - 混合认证模式 -### 集成测试 +### 集成测试(并没有) - 端到端认证流程 - API请求流程 - 事件处理 - 错误处理 -## 运行测试 - -### 运行所有认证架构测试 -```bash -npx ts-node scripts/run-auth-tests.ts -``` - -### 运行特定测试 -```bash -# SessionManager测试 -npx vitest run test/auth/session-manager.test.ts - -# AccessTokenManager测试 -npx vitest run test/auth/access-token-manager.test.ts - -# SessionAuthService测试 -npx vitest run test/auth/session-auth-service.test.ts - -# ExtendedApiClient测试 -npx vitest run test/api/extended-client.test.ts - -# 兼容性测试 -npx vitest run test/auth/compatibility.test.ts - -# 集成测试 -npx vitest run test/auth/integration.test.ts -``` - -### 运行覆盖率测试 -```bash -npx ts-node scripts/run-auth-tests.ts --coverage -``` - ## 测试环境 测试使用Vitest框架,配置了以下环境: @@ -102,16 +54,16 @@ npx ts-node scripts/run-auth-tests.ts --coverage 测试中使用了以下模拟对象: - ApiClient:模拟HTTP客户端 - SessionManager:模拟Session管理器 -- AccessTokenManager:模拟Access Token管理器 +- AccessTokenManager:模拟Access Token管理器(已废弃) - AuthEventManager:模拟认证事件管理器 -- TokenManager:模拟Token管理器(用于兼容性测试) +- TokenManager:模拟Token管理器(已废弃) ## 测试数据 测试使用以下模拟数据: - 用户信息 - Session信息 -- Access Token信息 +- Access Token信息(已废弃) - API响应数据 ## 断言 @@ -123,10 +75,3 @@ npx ts-node scripts/run-auth-tests.ts --coverage - 数据转换 - 安全性检查 -## 注意事项 - -1. 确保在运行测试前已安装所有依赖 -2. 测试使用模拟数据,不会影响实际系统 -3. 测试覆盖了主要功能和边界情况 -4. 测试验证了安全性和错误处理 -5. 测试确保了兼容性和平滑迁移 \ No newline at end of file diff --git a/test/setup.ts b/test/setup.ts index 5c51834..eef0028 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -5,7 +5,7 @@ import { vi } from 'vitest'; -// 模拟浏览器API +// 模拟浏览器API(其实用不到) Object.defineProperty(window, 'localStorage', { value: { getItem: vi.fn(), @@ -26,24 +26,24 @@ Object.defineProperty(window, 'sessionStorage', { writable: true }); -// 模拟IntersectionObserver +// 模拟IntersectionObserver(其实用不到) globalThis.IntersectionObserver = vi.fn().mockImplementation(() => ({ observe: vi.fn(), unobserve: vi.fn(), disconnect: vi.fn() })); -// 模拟ResizeObserver +// 模拟ResizeObserver(其实用不到) globalThis.ResizeObserver = vi.fn().mockImplementation(() => ({ observe: vi.fn(), unobserve: vi.fn(), disconnect: vi.fn() })); -// 模拟requestAnimationFrame +// 模拟requestAnimationFrame(其实用不到) globalThis.requestAnimationFrame = vi.fn((cb: FrameRequestCallback) => setTimeout(cb, 0)); -// 模拟cancelAnimationFrame +// 模拟cancelAnimationFrame(其实用不到) globalThis.cancelAnimationFrame = vi.fn((id: number) => clearTimeout(id)); // 模拟fetch diff --git a/tsconfig.json b/tsconfig.json index 4110ca1..49cdefd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,8 @@ "lib": ["es2022", "dom", "dom.iterable"], // Other Outputs - "sourceMap": true, + // 关了,用的时候老跳找不到源文件 + "sourceMap": false, "declaration": true, "declarationMap": true, diff --git a/types/chat/api.ts b/types/chat/api.ts index 093d146..e3b1f0e 100644 --- a/types/chat/api.ts +++ b/types/chat/api.ts @@ -3,11 +3,13 @@ import type { ChatMessageType } from './enum'; // 创建聊天会话请求接口 export interface CreateChatSessionRequest { + // 对方的ID,因为当前用户的id可以在session里获取(cookie自动带上) participantId: string; } -// 更新聊天会话请求接口 +// 更新聊天会话请求接口(占位) export interface UpdateChatSessionRequest { + // 只能用于处理后台逻辑 sessionId: string; } @@ -57,7 +59,7 @@ export interface MarkMessagesAsReadRequest { messageIds: string[]; } -// 标记消息已读响应接口 +// 标记消息已读响应接口(已读状态只面向接收方) export interface MarkMessagesAsReadResponse { success: boolean; markedMessageIds: string[]; // 成功标记的消息ID diff --git a/types/chat/base.ts b/types/chat/base.ts index 7bbe6d5..5044751 100644 --- a/types/chat/base.ts +++ b/types/chat/base.ts @@ -4,6 +4,7 @@ import type { ChatMessageType, ChatMessageStatus } from './enum'; // 聊天消息接口 export interface ChatMessage { id: string; + // 聊天会话id,下面那个接口的id sessionId: string; sender: User; receiver: User; @@ -16,6 +17,7 @@ export interface ChatMessage { fileName?: string; fileSize?: number; duration?: number; + // 小图预览 thumbnail?: string; [key: string]: unknown; }; diff --git a/types/chat/enum.ts b/types/chat/enum.ts index ebe5000..d925766 100644 --- a/types/chat/enum.ts +++ b/types/chat/enum.ts @@ -1,4 +1,4 @@ -// 聊天消息类型枚举 +// 聊天消息类型枚举(暂时用不到的) export enum ChatMessageType { TEXT = 'text', IMAGE = 'image', @@ -8,7 +8,7 @@ export enum ChatMessageType { SYSTEM = 'system' } -// 聊天消息状态枚举 +// 聊天消息状态枚举(暂时用不到的) export enum ChatMessageStatus { SENDING = 'sending', SENT = 'sent', diff --git a/types/model/base.ts b/types/model/base.ts index 1967067..0d37bd7 100644 --- a/types/model/base.ts +++ b/types/model/base.ts @@ -15,6 +15,7 @@ export interface ModelComment extends BaseEntity { author: BaseUser; // 作者信息 content: string; // 评论内容 parentId?: string; // 父评论ID,用于嵌套评论 + // 其实没必要 stats: ModelCommentStats; // 统计信息 } diff --git a/types/model/enum.ts b/types/model/enum.ts index c0d79bd..5466217 100644 --- a/types/model/enum.ts +++ b/types/model/enum.ts @@ -1,4 +1,5 @@ // AI模型评论排序枚举 +// 和post的重复了,而且字段还不一样 export enum CommentSortType { LATEST = 'latest', // 最新 HOTTEST = 'hottest', // 最热 diff --git a/types/post/base.ts b/types/post/base.ts index 6de154f..53c282d 100644 --- a/types/post/base.ts +++ b/types/post/base.ts @@ -10,10 +10,13 @@ export interface BaseEntity { // 用户基础信息接口 export interface BaseUser { + // 其实应该用profile的 user: User; } export interface PostComment extends BaseEntity { + // 这里没带postId,如果后台管理需要,就要带 + // 重复了,而且评论没有数据(点赞、收藏等等) authorId: string; // 作者ID author: BaseUser; // 作者信息 content: string; // 评论内容 diff --git a/types/post/enum.ts b/types/post/enum.ts index 74741c7..c30c058 100644 --- a/types/post/enum.ts +++ b/types/post/enum.ts @@ -1,3 +1,4 @@ +// 这里应该用type联合的,现在有冗余和风格不统一的问题 // 排序方式枚举 export enum SortOrder { ASC = 'asc', diff --git a/types/user/api.ts b/types/user/api.ts index cc16720..545358e 100644 --- a/types/user/api.ts +++ b/types/user/api.ts @@ -8,6 +8,7 @@ export interface GetHotAuthorsRequest { // 获取热门作者响应接口 export interface GetHotAuthorsResponse { + // 其实应该用profile返回的 data: User[]; // 数据列表 total: number; // 总数 } @@ -23,6 +24,7 @@ export interface GetAuthorRankingRequest { export interface GetAuthorRankingResponse { data: User[]; // 数据列表 total: number; // 总数 + // 其实冗余了 period: 'day' | 'week' | 'month'; // 统计周期 type: 'posts' | 'views' | 'likes'; // 排序类型 } diff --git a/types/user/base.ts b/types/user/base.ts index 10cd434..d783052 100644 --- a/types/user/base.ts +++ b/types/user/base.ts @@ -33,16 +33,17 @@ export interface ChangePasswordRequest { // 暂时用不到 newPassword: string; } -export interface RefreshTokenRequest { - sessionId: string; -} +// 目前只用得到session +// export interface RefreshTokenRequest { +// sessionId: string; +// } -export interface RefreshTokenResponse { - sessionId: string; -} +// export interface RefreshTokenResponse { +// sessionId: string; +// } -// 用户通知 -export interface UserNotification { // 暂时用不到 +// 用户通知(暂时用不到) +export interface UserNotification { id: string; userId: string; type: NotificationType; diff --git a/types/user/profile.ts b/types/user/profile.ts index 530726a..95c9168 100644 --- a/types/user/profile.ts +++ b/types/user/profile.ts @@ -20,11 +20,13 @@ export interface UserProfileUpdateResponse { // 用户关系 export interface UserRelation { + // 当前用户的id id: string; followerId: string[]; followingId: string[]; } +// 当前用户的id由cookie带上 export interface UserFollowRequest { userId: string; } diff --git a/types/user/search.ts b/types/user/search.ts index 939a544..7ad80e0 100644 --- a/types/user/search.ts +++ b/types/user/search.ts @@ -6,6 +6,7 @@ export interface UserSearchRequest { } export interface UserSearchResponse { + // 这里逻辑是对的(可惜暂时不打算做) users: Array total: number; } diff --git a/utils/data.ts b/utils/data.ts index 51f4bf6..9d38696 100644 --- a/utils/data.ts +++ b/utils/data.ts @@ -3,6 +3,23 @@ * @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, @@ -53,6 +70,7 @@ export const deepClone = (obj: T): T => { return new Date(obj.getTime()) as unknown as T; } + // 上面是边界情况判断,下面才是真的深拷贝逻辑 // 处理数组 if (Array.isArray(obj)) { return obj.map(item => deepClone(item)) as unknown as T; @@ -61,7 +79,9 @@ export const deepClone = (obj: T): T => { // 处理普通对象 const clonedObj = {} as T; for (const key in obj) { + // 最安全的一集 if (Object.prototype.hasOwnProperty.call(obj, key)) { + // 递归 clonedObj[key] = deepClone(obj[key]); } } @@ -88,6 +108,7 @@ export function deepEqual(a: unknown, b: unknown): boolean { 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; } @@ -101,6 +122,8 @@ export function deepEqual(a: unknown, b: unknown): boolean { * @param obj 源对象 * @param keys 要选取的属性键数组 * @returns 包含指定属性的新对象 + * 对象或数组类型属性:拷贝引用 + * 基本类型属性:直接赋值 */ export function pick, K extends keyof T>( obj: T, @@ -142,6 +165,7 @@ export function merge>(...objects: Partial[ for (const obj of objects) { if (obj && typeof obj === 'object') { + // 浅拷贝 Object.assign(result, obj); } } @@ -150,7 +174,7 @@ export function merge>(...objects: Partial[ } /** - * 将对象转换为查询字符串 + * 将对象转换为查询字符串(但是没用到) * @param obj 要转换的对象 * @returns 查询字符串 */ @@ -196,6 +220,7 @@ export const unique = ( keyFn?: (item: T) => K ): T[] => { if (!keyFn) { + // 只适用于简单数组(也是蛮神奇的语法) return [...new Set(array)]; } @@ -203,9 +228,11 @@ export const unique = ( return array.filter(item => { const key = keyFn(item); if (seen.has(key)) { + // 已存在,返回false过滤该元素 return false; } seen.add(key); + // 返回true保留该元素 return true; }); }; @@ -213,7 +240,7 @@ export const unique = ( /** * 数组分组 * @param array 要分组的数组 - * @param keyFn 分组键函数 + * @param keyFn 分组键函数(箭头函数实现) * @returns 分组后的对象 */ export const groupBy = ( @@ -233,7 +260,7 @@ export const groupBy = ( /** * 数组排序 * @param array 要排序的数组 - * @param compareFn 比较函数 + * @param compareFn 比较函数(箭头函数实现) * @returns 排序后的新数组 */ export const sortBy = ( @@ -248,6 +275,18 @@ export const sortBy = ( * @param func 要防抖的函数 * @param wait 等待时间(毫秒) * @param immediate 是否立即执行 + * + * 两种类型: + * 1. 立即执行:第一次触发立即执行,之后触发只重置等待时间 + * 2. 非立即执行:触发后等待时间结束才执行,期间触发会重置等待时间 + * + * immediate=true 立即执行 + * 第一次触发(callNow为true)->设定定时器(时间到了消除定时器),并立即执行 + * 期间多次触发->不断重置定时器的等待时间 + * + * immediate=false 非立即执行 + * 第一次触发(callNow为false)->设定定时器(时间到了调用函数) + * 期间多次触发->不断重置定时器的等待时间 */ export function debounce unknown>( func: T,