JavaScript Map对象键按值匹配及无序值对键实现方法咨询
实现按值匹配的Map及无序键Map方案
原生Map对对象类型的键采用SameValueZero比较规则,仅比较对象的内存引用地址,两个独立创建、结构完全相同的对象会被判定为不同的键,因此会出现你遇到的生成2个元素的问题,可通过以下方案实现按实际值匹配的Map:
方案1:序列化键实现(性能优先)
核心思路是将对象类型的键序列化为稳定的字符串,利用原生Map对原始值的等值比较能力实现按值匹配,序列化时对对象键做排序处理,天然支持无序对象键的匹配。
class ValueMap extends Map { // 私有方法:稳定序列化键 #serializeKey(key) { // 非对象类型和null直接返回原始值 if (typeof key !== 'object' || key === null) return key // 按对象键的字母序排序后序列化,避免键顺序不同导致序列化结果差异 return JSON.stringify(key, Object.keys(key).sort()) } set(key, value) { const serializedKey = this.#serializeKey(key) return super.set(serializedKey, value) } get(key) { const serializedKey = this.#serializeKey(key) return super.get(serializedKey) } has(key) { const serializedKey = this.#serializeKey(key) return super.has(serializedKey) } delete(key) { const serializedKey = this.#serializeKey(key) return super.delete(serializedKey) } }
测试效果:
let a = new ValueMap(); a.set({a: 1, b:3}, "Hello"); a.set({a: 1, b:3}, "World"); console.log(a.size) // 输出1,符合预期 console.log(a.get({b:3, a:1})) // 输出World,键顺序不同也可正常匹配
该方案的优缺点:
- 优点:性能接近原生Map,适合键数量大的场景
- 缺点:仅支持可JSON序列化的键,无法处理包含循环引用、函数、Symbol、undefined、BigInt等类型的键
方案2:深比较实现(兼容性优先)
核心思路是存储原始键值对,查找时对所有键做深相等判断,支持所有JS类型的键。
// 简易深相等判断函数,可根据需求扩展支持更多特殊类型 function deepEqual(a, b) { if (a === b) return true if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) return false const keysA = Object.keys(a), keysB = Object.keys(b) if (keysA.length !== keysB.length) return false for (const key of keysA) { if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false } return true } class DeepCompareMap { #entries = [] set(key, value) { const existingIndex = this.#entries.findIndex(([k]) => deepEqual(k, key)) if (existingIndex > -1) { this.#entries[existingIndex][1] = value } else { this.#entries.push([key, value]) } return this } get(key) { const entry = this.#entries.find(([k]) => deepEqual(k, key)) return entry ? entry[1] : undefined } has(key) { return this.#entries.some(([k]) => deepEqual(k, key)) } delete(key) { const existingIndex = this.#entries.findIndex(([k]) => deepEqual(k, key)) if (existingIndex > -1) { this.#entries.splice(existingIndex, 1) return true } return false } get size() { return this.#entries.length } }
该方案的优缺点:
- 优点:支持所有JS类型的键,可处理复杂结构、循环引用等特殊场景
- 缺点:查找、插入的时间复杂度为O(n),键数量较多时性能较差,适合键数量少的场景
无序值对作为键的专用实现
如果你的键是固定长度为2的无序值对(如[a,b]和[b,a]要判定为同一个键),可以单独做标准化处理:
class PairMap extends Map { #normalizePair(pair) { if (!Array.isArray(pair) || pair.length !== 2) throw new Error('键必须是长度为2的数组') // 对值对做稳定排序,保证顺序不同的同值对得到的键相同 return pair.sort().join(',') // 如果值对包含对象,可结合前面的序列化/深比较逻辑处理 } set(pair, value) { return super.set(this.#normalizePair(pair), value) } get(pair) { return super.get(this.#normalizePair(pair)) } has(pair) { return super.has(this.#normalizePair(pair)) } delete(pair) { return super.delete(this.#normalizePair(pair)) } } // 测试效果 const m = new PairMap() m.set([1,2], 'test') console.log(m.get([2,1])) // 输出test
内容的提问来源于stack exchange,提问作者Davo
相关产品推荐
相关产品推荐

