用闭包实现记忆化函数的核心是用私有缓存对象存储参数与结果,查缓存命中则返回,否则计算并存入;需注意参数序列化可靠性、this上下文处理及缓存清理策略。

用闭包实现函数结果的记忆缓存,核心是让内部函数“记住”之前计算过的输入和对应输出,避免重复执行耗时操作。关键在于闭包捕获并维护一个私有缓存对象(如 Map 或普通对象),在每次调用时先查缓存,命中则直接返回,未命中则计算、存入缓存再返回。
基础实现:用对象做缓存键值对
适合参数为字符串、数字等基本类型且无歧义的场景:
- 定义一个外层函数接收目标函数
fn,返回一个带缓存能力的新函数 - 在闭包内声明一个空对象
cache = {} - 返回的函数接收参数
...args,将参数转成唯一字符串键(例如args.join('_')) - 检查键是否已存在,存在就返回缓存值;否则调用
fn(...args),存入缓存并返回结果
示例:
function memoize(fn) {
const cache = {};
return function(arg) {
const key = String(arg);
if (key in cache) return cache[key];
cache[key] = fn(arg);
return cache[key];
};
}
<p>const expensiveAdd10 = memoize(x => {
console.log('计算中...');
return x + 10;
});</p><p>expensiveAdd10(5); // 打印"计算中...",返回15<br>expensiveAdd10(5); // 不打印,直接返回15立即学习“Java免费学习笔记(深入)”;
增强版:支持多参数与复杂类型(用 Map + JSON.stringify)
当参数含对象、数组或多个参数时,需更健壮的键生成方式:
- 改用
Map存储,避免对象键隐式转换问题 - 用
JSON.stringify(args)作为缓存键(注意:仅适用于可序列化的参数,且顺序敏感) - 若需支持函数、undefined、Symbol 等不可序列化值,需自定义键生成逻辑(如用 WeakMap 配合参数引用,但仅限单个对象参数)
示例:
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(...args);
cache.set(key, result);
return result;
};
}注意事项与边界情况
- 副作用函数不适用:如果原函数依赖外部状态(如修改全局变量、发请求、读时间),缓存会掩盖变化,导致逻辑错误
- 内存泄漏风险:缓存无限增长,长期运行需加限制(如 LRU 缓存、最大条目数、过期时间)
-
参数相等性陷阱:
{a:1} !== {a:1},但JSON.stringify({a:1}) === JSON.stringify({a:1}),看似合理实则忽略原型、函数属性、循环引用等问题 - 性能权衡:缓存本身有开销(序列化、查找),仅对真正昂贵且重复调用的函数才值得加 memoize
实用建议
- 优先使用成熟库如
lodash.memoize,它已处理参数扁平化、缓存上限、自定义 resolver 等细节 - 自己实现时,明确缓存策略:是否需要清除机制?是否允许多次调用不同参数组合?是否要支持异步函数?
- 测试时覆盖缓存命中/未命中、参数边界(null、undefined、NaN)、重复调用等场景


















