如何缓存接收含对象在内的多个参数的JavaScript函数?
实现支持含对象参数的函数缓存(Memoize)
普通的缓存实现(比如直接用Map存参数组合)会因为对象的引用特性失效——即使两个对象内容完全一致,只要是不同的引用,就会被当成不同的参数,导致缓存无法复用。针对你的场景,这里提供几种实用的解决方案:
方案1:序列化参数生成缓存键(通用场景)
如果你的参数对象没有循环引用、函数或Symbol这类无法被JSON序列化的内容,可以用JSON.stringify把所有参数转换成字符串作为缓存键,实现通用的memoize函数:
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; }; }
使用示例
function expensiveCalculation(a, b, options) { console.log('执行耗时计算'); return a + b + options.value; } const memoizedCalculation = memoize(expensiveCalculation); const options = { value: 10 }; console.log(memoizedCalculation(5, 3, options)); // 输出"执行耗时计算"和18 console.log(memoizedCalculation(5, 3, options)); // 直接输出18(复用缓存) // 内容相同的新对象也能复用缓存 console.log(memoizedCalculation(5, 3, { value: 10 })); // 直接输出18
注意事项
- 缺点:
JSON.stringify有一定性能开销,参数过大或调用极频繁时需要权衡 - 局限性:无法处理含函数、循环引用、Symbol的对象;对象属性顺序不同会生成不同键(比如
{a:1,b:2}和{b:2,a:1}会被当成不同参数)
方案2:序列化时忽略对象属性顺序
如果需要让属性顺序不同但内容一致的对象被识别为相同参数,可以自定义序列化逻辑,先对对象的键排序再序列化:
function serializeArgs(args) { return JSON.stringify(args, (key, value) => { if (typeof value === 'object' && value !== null && !Array.isArray(value)) { // 排序对象键,确保属性顺序不影响缓存键 return Object.fromEntries(Object.entries(value).sort()); } return value; }); } function memoize(fn) { const cache = new Map(); return function(...args) { const key = serializeArgs(args); if (cache.has(key)) { return cache.get(key); } const result = fn.apply(this, args); cache.set(key, result); return result; }; }
测试示例
console.log(memoizedCalculation(5, 3, { value:10, other:20 })); // 执行耗时计算,输出38 console.log(memoizedCalculation(5, 3, { other:20, value:10 })); // 直接输出38(复用缓存)
方案3:用WeakMap缓存固定引用的对象
如果你的参数对象是固定引用(比如始终传入同一个options对象),可以用WeakMap做缓存——它会在对象被垃圾回收时自动清理缓存项,避免内存泄漏:
function memoizeWithWeakMap(fn) { // 多层缓存:第一层存基本类型参数组合,第二层存对象的WeakMap const cache = new Map(); return function(a, b, options) { const baseKey = `${a}-${b}`; if (!cache.has(baseKey)) { cache.set(baseKey, new WeakMap()); } const objCache = cache.get(baseKey); if (objCache.has(options)) { return objCache.get(options); } const result = fn(a, b, options); objCache.set(options, result); return result; }; }
使用示例
const memoizedCalculation = memoizeWithWeakMap(expensiveCalculation); const options = { value:10 }; console.log(memoizedCalculation(5,3,options)); // 执行耗时计算,输出18 console.log(memoizedCalculation(5,3,options)); // 复用缓存,输出18 // 注意:新对象即使内容相同,也会重新计算 console.log(memoizedCalculation(5,3,{ value:10 })); // 执行耗时计算,输出18
方案选择建议
- 通用场景优先选方案2,能覆盖大多数普通对象参数的情况
- 如果参数对象是固定引用,选方案3更节省内存
- 性能要求极高时,可以给对象添加唯一标识(比如
id属性),用标识作为缓存键的一部分
内容的提问来源于stack exchange,提问作者sonu
相关产品推荐
相关产品推荐

