es-toolkit once 函数完全指南:限制函数仅执行一次并缓存结果
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
once是 es-toolkit 中用于限制函数只执行一次的实用函数:首次调用时真正执行并缓存返回值,后续所有调用直接返回缓存结果。它在es-toolkit/compat兼容层中与 Lodash 保持 1:1 的行为对齐,适用于初始化逻辑、事件绑定、数据库连接、API Token 获取等只需执行一次的昂贵操作。读完本文,你将掌握once的调用方式、返回值语义、源码级实现原理及其与主库版本的差异。
背景:es-toolkit/compat 兼容层
once的兼容版本位于es-toolkit/compat模块下,该模块镜像了 Lodash 的接口和行为,目标是让现有 Lodash 代码库无需改写调用点即可直接迁移到 es-toolkit。根据 compat 模块介绍,推荐的迁移路径是:先将lodash/lodash-es的导入路径替换为es-toolkit/compat,再逐步清理调用点、切换到严格类型的es-toolkit主入口。
本文讲解的 once(Lodash 兼容版) 在功能上与主库的 once 完全一致,兼容层只是为其提供了与 Lodash 相同的导入入口。
基本用法
once接收一个函数,返回一个"只能被调用一次"的新函数:
const limitedFunc = once(func);第一次调用时执行原函数;之后每次调用都不会再执行原函数,而是直接返回第一次调用的结果。
once(func)
使用once限制函数仅调用一次。首次调用后,结果被缓存,后续调用返回相同的值。
import { once } from 'es-toolkit/compat'; // 基础用法 let count = 0; const increment = once(() => { count++; console.log('Counter incremented:', count); return count; }); increment(); // 输出 'Counter incremented: 1',返回 1 increment(); // 无输出,返回 1 increment(); // 无输出,返回 1初始化函数实战示例
once特别适合创建昂贵的初始化操作或 setup 函数,例如数据库连接、API Token 初始化等:
import { once } from 'es-toolkit/compat'; // 实用示例 - 初始化函数 const initialize = once(() => { console.log('Initializing application...'); // 昂贵的初始化操作 return 'Initialization complete'; }); // 即使多次调用,初始化也只会执行一次 initialize(); // 输出 'Initializing application...' initialize(); // 无输出注意上例中两次调用返回的都是'Initialization complete'——即使初始化逻辑未被再次执行,缓存的结果也会被原样返回,保证调用方拿到的数据一致。
带参数与异步场景
从 主库 once 文档 可以看到,once同样适用于带参数的函数,以及返回 Promise 的异步函数:
import { once } from 'es-toolkit/function'; // 带参数的函数 const logOnce = once((message: string) => { console.log(`Important message: ${message}`); }); logOnce('Hello'); // 输出 'Important message: Hello' logOnce('Hello again'); // 无输出(已被调用过) logOnce('Hello once more'); // 无输出(已被调用过) // 异步场景:只有第一次调用会真正发起 API 请求 const fetchConfig = once(async () => { console.log('Fetching configuration'); const response = await fetch('/api/config'); return response.json(); }); const config1 = await fetchConfig(); const config2 = await fetchConfig(); // 返回缓存结果,不发起新请求在异步场景中,once缓存的是第一次调用返回的 Promise,因此第二次调用拿到的是同一个 Promise,不会重复请求。
参数与返回值
Parameters
func(Function):要限制为仅调用一次的函数。
Returns
(Function):返回一个新的受限函数。从第二次调用起,返回第一次调用的结果。
源码级实现原理
主库实现(src/function/once.ts)
once的核心实现非常简洁,位于 src/function/once.ts:
export function once<F extends (() => any) | ((...args: any[]) => void)>(func: F): F { let called = false; let cache: ReturnType<F>; return function (this: unknown, ...args: Parameters<F>): ReturnType<F> { if (!called) { called = true; cache = func.apply(this, args); } return cache; } as F; }实现要点:
- 通过闭包维护
called标志位和cache缓存变量,两个状态都保存在返回的新函数内部,互不干扰; - 第一次调用时,通过
func.apply(this, args)以调用方的this上下文和全部参数执行原函数,并将返回值存入cache; - 之后的每次调用跳过执行逻辑,直接
return cache,因此无论传入什么参数,返回的都是首次调用的结果; - 返回值类型
ReturnType<F>与参数类型Parameters<F>被完整保留,返回的受限函数在 TypeScript 类型上与传入函数一致(声明为F),保证了类型安全。
由于cache直接保存首次调用的返回值,once也适用于返回undefined或没有返回值的函数——缓存的是undefined,后续调用同样返回undefined。
兼容层实现(src/compat/function/once.ts)
兼容版本的实现更加精简,它只是对主库实现的直接转发:
import { once as onceToolkit } from '../../function/once.ts'; export function once<T extends (...args: any) => any>(func: T): T { return onceToolkit(func); }从源码结构看,src/compat/function/once.ts 将全部逻辑委托给主库实现,自身仅承担导出入口与类型签名适配的职责。这也是 compat 层"API 形状与 Lodash 一致、行为完全对齐"设计原则的体现。
once通过 src/compat/compat.ts 中的export { once } from './function/once.ts';对外导出,同时也可从独立的入口es-toolkit/compat/once导入(如import once from 'es-toolkit/compat/once'),后者在无 tree-shaking 的环境(CommonJSrequire()、React Native、无打包器的 Node.js)下只加载所需文件。
测试验证:行为边界的确认
主库测试 src/function/once.spec.ts 与兼容层测试 src/compat/function/once.spec.ts 共同验证了以下关键行为:
- 仅调用一次:使用
vi.fn()包装函数,多次调用后断言toHaveBeenCalledTimes(1),且每次返回值相同; - 忽略递归调用:在
once包装的函数内部再次调用自身,最终计数仍为 1,说明递归调用不会触发重复执行; - 异常只抛一次:如果原函数抛出异常,第一次调用抛错,后续调用不再抛错(因为
called已置为true,后续直接返回缓存的undefined); - 保留
this上下文:将once包装的函数作为对象方法调用时,this.value能正确取到对象属性,证明func.apply(this, args)正确透传了调用上下文; - 支持无返回值函数:返回
undefined的函数被多次调用时,原函数同样只执行一次。
这些测试用例覆盖了once的主要边界场景,可在迁移或改造时作为行为基准。
适用场景与使用建议
综合 兼容版文档 与主库 once 文档,once最典型的应用场景包括:
- 初始化 / setup 函数:应用启动时的配置加载、数据库连接池建立、日志系统初始化;
- API Token / 凭据获取:网络请求前的一次性鉴权令牌初始化,避免重复请求;
- 事件处理器:确保某个事件回调(如按钮点击、路由切换)只执行一次;
- 惰性单例:首次访问时计算结果并缓存,后续访问直接复用。
需要注意的是,once缓存的是首次调用的结果,与memoize(按参数缓存)不同,once不区分参数——后续调用即使传入不同参数,也只会拿到首次调用的结果。如果需要按参数区分缓存,应选用 es-toolkit 的 memoize;如果需要在限定次数(如 n 次之后)触发,可参考 after 等兼容函数。从主库的 function 模块索引 可以查看更多相关函数。
总结
once(func)返回一个受限函数:首次调用执行原函数并缓存结果,后续调用直接返回缓存值;- 兼容版
es-toolkit/compat的once与主库es-toolkit/function的once功能一致,前者仅为 Lodash 迁移场景提供入口; - 实现依赖闭包内的
called标志与cache缓存,通过apply(this, args)完整保留调用上下文; - 测试确认了"仅执行一次、忽略递归、异常只抛一次、保留 this、支持无返回值"等边界行为,可直接作为使用依据。
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考