
本文介绍在不依赖模块作用域的前提下,以最简洁、可读性最强的方式为 typescript 函数实现单例式内存缓存,重点对比函数对象自缓存、高阶 memoize 封装及箭头函数等方案,并给出生产就绪的推荐写法。
本文介绍在不依赖模块作用域的前提下,以最简洁、可读性最强的方式为 typescript 函数实现单例式内存缓存,重点对比函数对象自缓存、高阶 memoize 封装及箭头函数等方案,并给出生产就绪的推荐写法。
在 TypeScript 中实现函数结果的“首次调用计算、后续直接返回”的缓存逻辑(即 lazy singleton memoization),关键诉求是:零模块级变量污染、无冗余包装函数、语义清晰、类型安全且易于维护。常见的错误思路是将缓存状态挂载到闭包外的全局或模块变量上,这会破坏封装性;而原问题中通过 self = getDocumentRegistry 引用自身并挂载 .value 的方式虽可行,但存在重复赋值、可读性弱、类型提示缺失等问题。
更优雅的解法是采用纯函数式高阶缓存工具 + 一次性绑定。首先定义一个类型完备的 memoize 工具函数:
function memoize<T>(fn: () => T): () => T {
let cached: T | undefined;
let hasCached = false;
return () => {
if (hasCached) return cached!;
cached = fn();
hasCached = true;
return cached;
};
}该实现避免了将缓存属性附加到函数对象(如 fn.value)带来的类型隐患(TypeScript 默认不认为函数具有任意自有属性),同时保证了严格的单次执行语义和良好的类型推导。
接着,直接将其应用于目标函数——无需命名中间函数,也无需后缀区分:
完整的Galileo AI平台 TypeScript/JS SDK 参考,用于评估、观察和保护 GenAI 应用。适用于构建 Node.js 或 TypeScript 项目。
// ✅ 推荐:简洁、类型安全、语义明确 const getDocumentRegistry = memoize(() => ts.createDocumentRegistry()); // ✅ 等价写法(若 ts.createDocumentRegistry 无参数且为纯函数) const getDocumentRegistry = memoize(ts.createDocumentRegistry);
这种写法的优势在于:
- 零模块污染:缓存状态完全封闭在 memoize 返回的闭包内;
- 极致简洁:一行声明完成定义与缓存绑定,无重复逻辑;
- 类型自动推导:getDocumentRegistry 的返回类型由 ts.createDocumentRegistry() 自动推断,无需手动标注;
- 可测试友好:未缓存版本(原始函数)仍可独立导入/调用(如需单元测试验证初始化逻辑);
- 无运行时开销:仅首次调用触发实际计算,后续为纯属性访问。
⚠️ 注意事项:
- 不要使用 function getDocumentRegistry() { ... } 声明后传入 memoize——这会导致函数名被保留但语义割裂,且无法享受箭头函数的简洁性;
- 避免 fn.cache ?? (fn.cache = ...) 模式:它绕过 TypeScript 类型检查(需 // @ts-ignore 或 any 断言),破坏类型安全性,且在严格模式或某些打包器下可能引发意外行为;
- 若需支持带参数的缓存(如 memoizeWithArgs),应改用 Map 或 WeakMap 实现多键缓存,但本场景中 ts.createDocumentRegistry() 无参数,单例缓存已足够。
总结:对于无参工厂函数的单次初始化缓存,const fn = memoize(() => ...) 是目前 TypeScript 生态中最平衡——兼顾简洁性、类型安全、可维护性与性能——的标准实践。

















