闭包缓存递归结果可行,但递归调用必须走缓存函数本身;常见错误是仅顶层调用缓存而内部仍调用原始函数,正确做法是将缓存函数赋值给原函数名或通过闭包重绑定。

直接用闭包缓存递归结果是可行的,但必须让递归调用走缓存函数本身,否则缓存形同虚设。
递归函数必须自调用缓存版本
常见错误是只对顶层调用做 memoize,而内部递归仍调用原始函数。比如 fibonacci 未改造时,fibonacci(10) 内部仍会反复调用未缓存的 fibonacci(8)、fibonacci(7) 等。
- 正确做法:把缓存后的函数赋值给原函数名(或通过闭包重绑定),确保所有递归调用都命中缓存
- 错误写法:
const memoFib = memoize(fibonacci); memoFib(10)—— 若fibonacci内部仍调用自己,缓存无效 - 推荐结构:用 IIFE 或高阶函数封装,使递归体天然使用缓存逻辑,例如
const fibonacci = memoize((n) => n
缓存键必须能区分不同参数组合
递归常带多个参数(如 lcs(str1, str2, i, j)),若只用 JSON.stringify(args) 作键,容易因 undefined、function、循环引用或浮点精度丢失导致误命中或崩溃。
- 单参数整数/字符串:可用对象字面量
cache[n],最快最安全 - 双参数且类型稳定:推荐
`${a}|${b}`拼接,避免 JSON 序列化开销和陷阱 - 多参数或含对象:用
Map+ 自定义键生成函数,例如mapKey(...args) { return args.join('|'); },但需提前校验参数是否可安全拼接
闭包缓存不自动清理,递归深度大时易内存溢出
斐波那契第 50 项缓存 50 个键值对没问题,但像树遍历、动态规划类递归可能产生上万级键,长期驻留内存会拖慢 GC,甚至 OOM。
- 简单限制:加计数器,超过阈值后清空
cache = new Map()(适合生命周期短的场景) - 更稳妥:用 LRU 缓存(如
lru-cache库),但注意它不内置闭包,需手动注入到 memoize 逻辑中 - 警惕“永远不释放”的闭包:若缓存函数被全局变量引用,或绑定到事件监听器中,
cache会一直存活
真正卡住性能的往往不是计算本身,而是缓存没生效或缓存键设计错位——尤其是递归中参数看似相同、实则上下文不同(比如带副作用的状态标记)。动手前先在控制台打日志确认:每次递归调用是否真的 hit 了 cache.has(key)。










