once函数用闭包封装called标记和result缓存,首次调用执行原函数并保存结果,后续调用直接返回缓存值;支持this绑定与参数透传,可通过try/catch缓存错误或添加reset方法增强功能。

用闭包实现 once 函数,核心是让内部状态(如是否已执行)在函数调用间保持私有且不被重置。每次调用返回的函数时,先检查状态,已执行则直接返回缓存结果,否则执行原函数并标记为“已完成”。
基础 once 实现(返回新函数)
这是最常见写法:接收一个函数 fn,返回一个最多只执行一次的包装函数。
- 闭包捕获两个私有变量:
called(布尔标记)和result(缓存返回值) - 首次调用时执行
fn(),保存结果并设called = true - 后续调用跳过执行,直接返回上次的
result
示例代码:
function once(fn) {
let called = false;
let result;
return function(...args) {
if (called) return result;
result = fn.apply(this, args);
called = true;
return result;
};
}
<p>// 使用
const logOnce = once(console.log);
logOnce('hello'); // 输出 'hello'
logOnce('world'); // 无输出,也不报错</p>
支持 this 和参数透传
上面的实现已用 ...args 和 fn.apply(this, args) 正确处理了 this 绑定与任意参数,适用于普通函数、方法调用等场景。
- 若原函数依赖
this(如对象方法),必须用apply或call保证上下文正确 - 箭头函数无法正确绑定
this,所以once内部不能用箭头函数定义返回函数
更健壮的变体(可重置 / 支持错误缓存)
默认 once 不区分成功/失败。若需缓存异常结果(比如首次调用抛错,后续也直接抛同一错),只需保留 result 并记录是否出错,或统一用 try/catch 包裹执行逻辑。
- 想支持手动重置?可在返回函数上挂载
.reset()方法,修改闭包内变量 - 想限制执行 *恰好* 一次(第二次调用报错而非静默忽略)?把
if (called) return result改成if (called) throw new Error('Already called')
ES6 简洁写法(无变量声明)
利用解构 + 默认值 + 短路逻辑,可写出更紧凑版本(适合工具函数库):
const once = (fn) => {
let cache;
return (...args) => (cache ??= fn(...args));
};
注意:??= 是空值赋值操作符,仅当 cache 为 null 或 undefined 时才执行赋值。它隐式完成了“首次执行并缓存”的逻辑,但不处理 this 和异常 —— 适用纯函数场景。











