闭包可实现通用缓存装饰器,通过参数序列化为键、Map存储结果,首次调用执行函数并缓存,后续相同参数直接返回;基础版用JSON.stringify,增强版需处理this、特殊类型及缓存清理。

可以用闭包保存缓存数据,结合函数参数序列化作为键,实现一个通用的缓存装饰器。核心是让被装饰函数只在首次调用时执行,后续相同参数直接返回缓存结果。
缓存装饰器的基本结构
装饰器接收目标函数,返回一个新函数;新函数内部维护一个 Map(或普通对象)存储参数组合到结果的映射。闭包确保缓存不被外部访问,又能在多次调用间持续存在。
- 用 Map 比对象更安全:支持任意类型键(如对象、函数、Symbol),避免 toString 冲突
- 参数需可序列化为唯一键:简单场景可用
JSON.stringify(args);含函数、undefined、Symbol 或循环引用时需自定义序列化逻辑 - 注意引用相等性问题:两个内容相同的对象
{a:1}和{a:1}序列化后相同,但若需区分原始引用,应改用 WeakMap + 参数数组哈希(较复杂,一般不需要)
基础版:支持基本类型参数
适用于参数全是原始值(string/number/boolean/null/undefined)或简单嵌套对象的场景:
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
使用示例:
立即学习“Java免费学习笔记(深入)”;
const expensiveAdd = memoize((a, b) => {
console.log('计算中...');
return a + b;
});
expensiveAdd(2, 3); // 输出“计算中...”,返回 5
expensiveAdd(2, 3); // 直接返回 5,不打印
增强版:支持函数内 this 和更健壮的键生成
当原函数依赖 this 或参数含不可序列化值(如 Date、RegExp)时,需更谨慎处理:
- 保留
this上下文:用fn.call(this, ...args)而非apply确保严格一致 - 对特殊值预处理:例如
new Date()→date.getTime(),/abc/g→regexp.toString() - 避免 JSON.stringify 的陷阱:undefined、function、Symbol 会被忽略或转成 null,可改用第三方库(如 fast-json-stable-stringify)或手写简易序列化器
可选扩展:缓存清理与配置
实际项目中常需控制缓存生命周期:
- 添加
clear()方法暴露给外部手动清空 - 支持最大缓存数量(LRU):用 lru-cache 或自己维护队列
- 支持 TTL(过期时间):每个缓存项记录时间戳,读取时判断是否过期
- 导出缓存 Map 供调试:比如开发时打印
memoizedFn.cache.size


















