闭包实现缓存函数的核心是在外部函数中创建私有缓存对象,内部函数执行前先查缓存,命中则返回结果,未命中则计算、存入缓存再返回;基础版支持单参数字符串键,健壮版用JSON.stringify处理多参数并特殊处理undefined/null/Symbol,进阶版支持自定义哈希、LRU淘汰和异步函数缓存,需注意引用类型、this绑定、内存泄漏及纯函数要求。

用闭包实现缓存函数(memoization)的核心思路是:在外部函数中创建一个私有缓存对象(如 Map 或普通对象),内部返回的函数在执行前先查缓存,命中则直接返回结果,未命中则计算、存入缓存再返回。
基础版本:支持单参数、字符串键
适用于参数能安全转为字符串(如数字、字符串、布尔值)的简单场景:
function memoize(fn) {
const cache = new Map();
return function(arg) {
const key = String(arg);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(arg);
cache.set(key, result);
return result;
};
}
<p>// 使用示例
const fib = memoize(n => n <= 1 ? n : fib(n - 1) + fib(n - 2));
console.log(fib(10)); // 快速计算,重复调用不重算
健壮版本:支持多参数 + JSON 序列化键
当函数接收多个参数,且参数类型较复杂(如对象、数组)时,可用 JSON.stringify 生成缓存键(注意:需确保参数可序列化,且无函数、undefined、Symbol 等):
- 用
JSON.stringify([...arguments])或JSON.stringify(args)生成唯一键 - 缓存仍用
Map更安全(避免原型污染和非字符串键问题) - 对原始值(如
null、undefined)需特殊处理,因为JSON.stringify(undefined)是undefined,不能作为键
function memoize(fn) {
const cache = new Map();
return function(...args) {
// 处理 undefined、null 等无法被 JSON.stringify 正确表示的值
const key = JSON.stringify(args.map(a =>
a === undefined ? 'undefined' :
a === null ? 'null' :
typeof a === 'symbol' ? `symbol:${String(a)}` :
a
));
<pre class="brush:php;toolbar:false;">if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;}; }
进阶优化:支持自定义哈希函数与缓存淘汰
生产环境常用方案会引入更多控制能力:
- 允许传入
hashFn自定义键生成逻辑(比如只取前两个参数,或忽略某个字段) - 搭配
LRU Cache(如lru-cache库或手写 LRU)限制缓存大小,防止内存泄漏 - 支持异步函数(
async/await)的 memoization,需缓存 Promise 实例而非等待结果(避免重复触发异步请求)
简易 LRU 风格示意(不依赖外部库):
function memoize(fn, max = 100) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
const value = cache.get(key);
cache.delete(key); // 移到末尾(模拟访问更新)
cache.set(key, value);
return value;
}
const result = fn(...args);
if (cache.size >= max) {
const firstKey = cache.keys().next().value;
cache.delete(firstKey);
}
cache.set(key, result);
return result;
};
}
注意事项与陷阱
闭包 memoization 看似简单,但实际使用需留意:
- 引用类型参数(如对象、数组)若内容相同但不是同一引用,
JSON.stringify可能适用,但要注意循环引用会报错 - 函数内使用
this时,闭包返回的函数会丢失原始上下文,需用fn.apply(this, args)显式绑定 - 缓存长期存在,若参数量大或生命周期长,可能造成内存堆积 —— 按需设置最大容量或提供
clear()方法 - 副作用函数(如修改全局状态、发请求)不适合无条件缓存;应确保被 memoize 的函数是纯函数


















