如何实现可识别键相同但顺序不同对象的memoize缓存函数
问题原因
- 原生
JSON.stringify序列化普通对象时会保留键的定义顺序,两个键完全相同但顺序不同的对象会生成完全不同的序列化结果,导致缓存key不匹配,无法命中缓存。
解决方案
核心思路是实现一个稳定序列化函数,序列化对象时先对键进行排序,保证键序不同但内容相同的对象能生成相同的序列化结果,再替换原有memoize中生成key的逻辑即可。
完整实现代码
// 稳定序列化函数,保证同内容不同键序的对象输出相同结果 const stableStringify = (value) => { // 处理基础类型、null、undefined if (typeof value !== 'object' || value === null) { // 单独处理特殊基础类型,避免序列化结果歧义 if (value === undefined) return 'undefined' if (typeof value === 'number' && isNaN(value)) return 'NaN' if (typeof value === 'number' && !isFinite(value)) return value > 0 ? 'Infinity' : '-Infinity' return JSON.stringify(value) } // 处理日期类型 if (value instanceof Date) { return `Date(${value.toISOString()})` } // 处理正则类型 if (value instanceof RegExp) { return `RegExp(${value.toString()})` } // 处理数组类型 if (Array.isArray(value)) { return `[${value.map(item => stableStringify(item)).join(',')}]` } // 处理普通对象:先排序键再序列化 const sortedKeys = Object.keys(value).sort() const objStr = sortedKeys.map(key => { return `${stableStringify(key)}:${stableStringify(value[key])}` }).join(',') return `{${objStr}}` } const memoize = fn => { const cache = Object.create(null); // 用空对象避免原型链属性干扰 return (...args) => { // 用稳定序列化生成key const key = stableStringify(args) if (!(key in cache)) { cache[key] = fn(...args) } return cache[key] } } // 测试代码 const stringifyObj = (obj) => { console.log(obj) return JSON.stringify(obj) } const myMemo = memoize(stringifyObj) const a = myMemo({ hello: "world", goodnight: "moon" }) const b = myMemo({ goodnight: "moon", hello: "world" }) // 控制台仅打印一次对象,说明缓存命中符合预期
扩展说明
- 上述实现支持任意数量、任意类型的参数,包括嵌套对象、数组、日期、正则、NaN、Infinity等特殊场景
- 如果需要处理循环引用的对象,可以在
stableStringify中加入WeakSet记录已遍历的对象,避免递归爆栈 - 如果对性能要求极高,也可以用
Map结合自定义哈希逻辑代替字符串key,减少大对象序列化的性能开销
内容的提问来源于stack exchange,提问作者Dan Zuzevich
相关产品推荐
相关产品推荐

