当被缓存函数接收函数参数时,如何指定Lodash memoize的缓存键?
如何给_.memoize指定合适的缓存键来复用过滤函数结果?
这个问题我太熟了——用函数的字符串表示当缓存键确实坑点满满:比如函数重定义后字符串可能变化、匿名函数哪怕逻辑完全相同但引用不同会生成不同字符串,甚至代码压缩后函数的toString结果还会被混淆,完全没法保证缓存的可靠性。下面给你几个更靠谱的方案,按需选择:
方案1:给过滤函数手动添加唯一标识
这是最直接可控的方案,适合你能自己定义过滤函数的场景。给每个过滤函数加一个自定义的唯一属性,然后让_.memoize的resolver函数读取这个属性作为缓存键:
// 定义过滤函数时,手动添加唯一标识 const filterA = (item) => item.value > 10; filterA.filterId = 'filter_a'; const filterB = (item) => item.value < 5; filterB.filterId = 'filter_b'; // 自定义resolver,用filterId作为缓存键 const memoizedM = _.memoize(m, (filterFn) => filterFn.filterId);
只要你保证每个过滤函数的filterId唯一,就能精准命中缓存,完全不会有字符串表示带来的各种问题。
方案2:用WeakMap手动管理缓存
_.memoize默认用普通对象存储缓存,会把函数键自动转成字符串,这也是你之前方案出问题的根源。而WeakMap可以直接用函数引用作为键,还能自动回收不再使用的函数对应的缓存,避免内存泄漏:
const cache = new WeakMap(); function memoizedM(filterFn) { if (cache.has(filterFn)) { return cache.get(filterFn); } // 执行原始的m函数,处理同一数据集 const result = m(filterFn); cache.set(filterFn, result); return result; }
这个方案不需要修改任何过滤函数,只要函数的引用不变,就能命中缓存。唯一的小局限是:如果是两个逻辑完全相同但不同引用的函数(比如两次定义同一个匿名函数),会被当成不同的缓存键,无法共享结果。
方案3:基于函数逻辑生成唯一哈希
如果你的场景需要让逻辑完全相同但引用不同的过滤函数共享缓存,那可以把函数的抽象语法树(AST)转成哈希值作为缓存键。这样不管函数引用怎么变,只要逻辑一致就能命中缓存:
const { parse } = require('@babel/parser'); const generate = require('@babel/generator').default; const crypto = require('crypto'); // 生成函数逻辑的唯一哈希 function getFunctionHash(fn) { // 把函数转成AST const ast = parse(fn.toString(), { sourceType: 'module' }); // 移除不影响逻辑的信息:函数名、位置信息 if (ast.program.body[0].id) ast.program.body[0].id = null; delete ast.program.body[0].loc; // 把AST转回标准化的代码字符串 const normalizedCode = generate(ast).code; // 生成MD5哈希作为唯一键 return crypto.createHash('md5').update(normalizedCode).digest('hex'); } // 用哈希值作为缓存键 const memoizedM = _.memoize(m, (filterFn) => getFunctionHash(filterFn));
这个方案的缓存命中率最高,但需要额外引入babel相关依赖,而且计算哈希有一定性能开销,适合对缓存命中率要求极高的场景。
总结选择建议
- 优先选方案1:简单、可靠、无额外开销,只要能控制过滤函数的定义就用它;
- 不想修改过滤函数选方案2:基于引用缓存,实现简单还能自动回收内存;
- 需要逻辑相同的函数共享缓存选方案3:虽然麻烦但能解决最复杂的缓存匹配场景。
内容的提问来源于stack exchange,提问作者fstr
相关产品推荐
相关产品推荐

