
es-toolkit once 函数详解让函数只执行一次的 Lodash 兼容方案【免费下载链接】es-toolkitA modern JavaScript utility library thats 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkitonce是 es-toolkit 中用于限制函数只执行一次的实用工具位于es-toolkit/compatLodash 兼容入口之下与主库es-toolkit/function中的once功能完全一致。本文将以 docs/ja/compat/reference/function/once.md 为主体结合 src/function/once.ts 的实现与 src/function/once.spec.ts、src/compat/function/once.spec.ts 的测试用例系统讲解once的用法、参数约定、底层原理与边界行为帮助你在初始化、事件绑定、缓存构建等场景中正确使用它。一、once 是什么限制函数只调用一次once接收一个函数返回一个受限的新函数该新函数只会真正执行原函数一次第二次及以后的调用不再执行原函数体而是直接返回第一次调用的结果。const limitedFunc once(func);在 Lodash 兼容文档中once的语义被概括为参数funcFunction即需要被限制为只调用一次的函数。返回值一个只会调用一次的新函数从第二次调用开始返回第一次调用的结果。这种首次执行、后续命中缓存的行为非常适合成本较高的初始化操作与一次性设置逻辑例如数据库连接、API 令牌初始化、应用启动配置等——无论被调用多少次真正的副作用只会发生一次同时后续调用依然能拿到一致的返回值。二、基本用法从es-toolkit/compat导入once即可使用import { once } from es-toolkit/compat; // 基本用法 let count 0; const increment once(() { count; console.log(カウンター増加:, count); return count; }); increment(); // 输出 カウンター増加: 1返回 1 increment(); // 不输出任何内容返回 1 increment(); // 不输出任何内容返回 1从输出可以看到只有第一次调用真正执行了increment的函数体count与console.log后续调用均直接返回缓存的第一次结果1count不再增长。实战示例一次性初始化最常见的应用场景是只允许初始化一次import { once } from es-toolkit/compat; // 实用示例 —— 初始化函数 const initialize once(() { console.log(アプリケーション初期化中...); // 成本高昂的初始化操作 return 初期化完了; }); // 无论调用多少次初始化只执行一次 initialize(); // 输出 アプリケーション初期化中... initialize(); // 不输出任何内容该模式同样适用于主库es-toolkit/function入口主库文档 docs/reference/function/once.md 中还给出了带参数的示例once((message: string) ...)包装的日志函数只有第一次调用会打印消息后续即使传入不同参数也不再执行函数体。三、参数与返回值约定once(func)的完整约定如下项目说明参数funcFunction类型需要被限制为只调用一次的函数返回值Function返回一个只会调用一次的新函数从第二次调用起返回第一次调用的结果值得注意的是返回的新函数保留了原函数的参数签名与this上下文。源码中通过 TypeScript 泛型约束F extends (...args: any[]) any保持了类型完整性返回值类型仍为F因此原函数的参数类型与返回类型不会丢失。四、compat 入口与主库 once 的关系兼容文档开头特别提示es-toolkit/compat下的这个once与主库 once 功能相同。这一结论在源码中得到直接印证——src/compat/function/once.ts 是一个薄包装层import { once as onceToolkit } from ../../function/once.ts; export function onceT extends (...args: any) any(func: T): T { return onceToolkit(func); }也就是说es-toolkit/compat的once只是把调用转发给主库 src/function/once.ts 的实现两者行为完全一致不存在两套逻辑。该函数通过 src/compat/compat.ts 的export { once } from ./function/once.ts统一对外暴露因此可以从es-toolkit/compat直接导入。提示如果你不需要 Lodash 兼容命名空间直接从es-toolkit/function导入主库once即可二者可互换使用。五、源码实现深度解析主库实现位于 src/function/once.ts核心逻辑非常精简export function onceF extends (() any) | ((...args: any[]) void)(func: F): F { let called false; let cache: ReturnTypeF; return function (this: unknown, ...args: ParametersF): ReturnTypeF { if (!called) { called true; cache func.apply(this, args); } return cache; } as F; }其工作原理可以从以下几点理解闭包状态called是否已调用与cache首次调用结果保存在闭包中不污染全局也不依赖外部存储。每次once调用都会创建独立的闭包状态。先置位、后执行进入分支后先将called置为true再执行func.apply(this, args)并缓存结果。这个顺序是防重入的关键。保留this与参数使用func.apply(this, args)调用原函数因此返回的新函数无论以普通函数还是对象方法形式调用this与实参都会被正确传递参见测试中的this保留用例。统一返回缓存无论是否首次调用最终都返回cache保证所有调用结果一致。边界行为一异常只抛一次called先置位再执行的设计带来一个有趣的边界行为如果第一次调用时原函数抛出异常那么这次调用会照常抛错但由于called已变为true后续调用不会再执行原函数也就不会再次抛出异常。这在 src/compat/function/once.spec.ts 中有专门用例验证it(should not throw more than once, () { const resultFunc once(() { throw new Error(); }); expect(resultFunc).toThrow(); // 第一次抛出 expect(resultFunc).not.toThrow(); // 之后不再抛出 });边界行为二忽略递归调用同样得益于先置位策略如果原函数内部递归调用被包装后的函数递归调用会因called true而被直接短路返回此时cache尚未赋值返回undefined从而避免无限递归。对应测试见 src/compat/function/once.spec.tsit(should ignore recursive calls, () { let count 0; const resultFunc once(() { resultFunc(); // 递归调用被忽略 return count; }); expect(resultFunc()).toBe(1); expect(count).toBe(1); // 只真正执行了一次 });边界行为三返回值可为 undefinedonce对返回undefined的函数同样适用——即使首次调用没有返回值后续调用依然不会重复执行函数体。主库测试 src/function/once.spec.ts 验证了这一点这对日志、事件绑定、DOM 操作等仅副作用、无返回值的函数非常重要。六、测试验证与行为保证仓库为once提供了完整的测试覆盖可以作为行为契约参考src/function/once.spec.ts主库实现的测试覆盖只调用一次、返回 undefined 的函数、无返回值的函数以及保留 this 上下文四个维度并使用 Vitest 的vi.fn精确断言原函数被调用的次数。src/compat/function/once.spec.tscompat 入口的测试额外覆盖忽略递归调用、异常只抛一次、保留 this 上下文等边界场景。如果你希望在项目中验证once的行为可在仓库根目录运行现有的 Vitest 测试环境配置见 vitest.config.mts对once相关用例进行断言确认函数体实际执行次数始终为 1。七、使用建议与注意事项综合文档、源码与测试使用once时有几点建议适用于一次性副作用数据库连接建立、API 令牌初始化、应用配置加载、事件处理器注册等只应发生一次的逻辑是once的理想场景。结果一致性保证由于后续调用返回缓存的首次结果once包装的函数可以放心在多个模块间共享无需担心重复初始化导致的资源浪费或状态不一致。异常语义需留意如果首次调用抛出异常后续调用将不再执行原函数也不会再次抛错而是静默返回undefined。若初始化失败后需要重试应自行在函数体内处理重试逻辑而不是依赖再次调用包装函数。与 Lodash 语义对齐once在es-toolkit/compat中与 Lodash 同名函数保持兼容且与主库实现共享同一份代码src/compat/function/once.ts 转发至 src/function/once.ts在迁移 Lodash 代码时可以直接替换导入路径。【免费下载链接】es-toolkitA modern JavaScript utility library thats 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考