once包装器通过闭包私有化called状态与result结果,确保函数全局仅执行一次;支持this绑定、任意参数、Promise缓存及错误复用,避免全局变量、fn置空等常见错误。

直接用闭包封装一个 once 包装器,就能让任意函数全局只执行一次——它靠私有状态标记 + 结果缓存,不依赖全局变量,也不需要手动管理生命周期。
核心写法:闭包锁定 called 状态
闭包让 called 和 result 长期驻留,外部无法修改。返回的新函数每次调用时先检查状态,未执行才运行原函数并保存结果:
- 声明
let called = false和let result,二者都在闭包内私有化 - 返回函数中判断
!called,为真则执行fn.apply(this, arguments)并设called = true - 后续调用跳过执行,直接返回
result(含对象、Promise 或 undefined)
支持 this 绑定和多参数
初始化逻辑常依赖上下文或传参,比如类方法或带配置的工厂函数。正确实现必须保留 this 和展开参数:
- 用
fn.apply(this, arguments)而不是fn(),确保内部this指向调用者 - 接收
...args或直接用arguments,兼容任意参数个数和类型 - 避免硬编码参数列表,否则传参不灵活(如
once(fn)(a, b, c)会失败)
处理异步初始化(Promise 场景)
加载配置、请求 token、创建 Worker 等操作通常是异步的。若不加处理,多次调用可能并发触发请求:
立即学习“Java免费学习笔记(深入)”;
- 首次调用返回 Promise,后续调用应复用该 Promise(而非新建),避免重复请求
- 可扩展 once 实现:捕获错误并缓存,后续调用直接 reject 同一错误,不重试
- 示例:初始化 API 客户端时,
once(() => fetch('/config').then(r => r.json()))只发一次请求
别踩这些坑
看似简单,但实际容易出错:
- 不要用全局变量(如
window.hasInit)替代闭包——易被覆盖、跨模块冲突 - 别在返回函数里删掉
fn = null来“清空函数”,这会丢失this绑定且不可靠 - 如果初始化返回对象,注意它是同一引用;需深拷贝请在首次返回前处理,而不是每次返回新副本
- 事件监听等场景优先用原生
{ once: true },不必自己封装 once


















