
本文介绍一种不依赖模块级变量、不使用包装函数的 TypeScript 函数记忆化方案,通过直接复用函数对象自身作为缓存容器,并结合高阶 memoize 工具函数,实现语义清晰、零冗余、可复用的记忆化逻辑。
本文介绍一种不依赖模块级变量、不使用包装函数的 typescript 函数记忆化方案,通过直接复用函数对象自身作为缓存容器,并结合高阶 `memoize` 工具函数,实现语义清晰、零冗余、可复用的记忆化逻辑。
在 TypeScript(或 JavaScript)中,为纯函数添加记忆化(memoization)能力时,常见的做法是引入模块级缓存变量(如 let cache: ReturnType
为此,我们定义一个通用、类型安全的 memoize 工具函数:
function memoize<t>(fn: () => T): () => T {
return function thisIsMemoized() {
// 利用 this 指向调用该函数的对象(即函数自身)
const cached = (this as typeof fn).cachedValue;
if (cached !== undefined) return cached;
const result = fn();
(this as typeof fn).cachedValue = result;
return result;
} as unknown as () => T;
}</t>
✅ 类型提示友好:返回函数与原函数签名一致;
✅ 零模块污染:缓存完全内聚于函数实例;
✅ 无重复逻辑:避免 self = fn; self.cached ??= ... 的样板代码。
使用方式极其简洁:
const getDocumentRegistry = memoize(() => ts.createDocumentRegistry()); // 或更进一步(若 ts.createDocumentRegistry 不接受参数): const getDocumentRegistry = memoize(ts.createDocumentRegistry);
调用时行为完全透明:
使用 Scrapfly SDK(scrapfly-sdk)在 TypeScript 或 JavaScript 中进行网页抓取,用于用户请求抓取 URL、绕过反爬虫防护、渲染页面等场景。
console.log(getDocumentRegistry()); // 第一次执行,创建并缓存 registry console.log(getDocumentRegistry()); // 直接返回缓存值,无额外开销
⚠️ 注意事项:
- 该方案仅适用于无参函数(() => T)。若需支持带参记忆化(如 memoize string>),则需引入 Map 或 WeakMap 实现参数键哈希,此时函数对象自身不再适合存储多值缓存;
- this 绑定依赖于直接调用语法(fn()),若通过 call/apply/bind 或箭头函数间接调用,this 将不指向函数本身,导致缓存失效——因此请始终以普通函数调用形式使用;
- 若需同时暴露缓存版与原始版函数(极少数调试场景),可保留原始函数名,并显式导出缓存版本(如 export const getDocumentRegistry = memoize(...)),无需添加 Cached 后缀——API 消费者只应关心“获取 registry”,而非其实现是否缓存。
总结:相比在函数体内手动维护 self.value 或依赖外部模块变量,memoize(fn) 提供了真正正交、可组合、类型安全的记忆化抽象。它把缓存逻辑从业务函数中彻底剥离,让 getDocumentRegistry 的定义回归语义本质——“获取文档注册表”,而性能优化则由工具函数静默承担。这是函数式编程思想在 TypeScript 工程实践中的轻量落地。










