Map
和 Object
都是用于存储键值对的数据结构,但它们之间存在许多关键区别。
简单来说,Map
是一个更专门化、功能更强大的键值对集合,而 Object
则是一个更通用的、用于描述实体的基础数据结构。
下面我们从多个维度来详细对比它们的区别。
核心区别对比表
特性 | Map | Object |
---|---|---|
键的类型 | 任意类型(函数、对象、基本类型等) | 只能是 String 或 Symbol(其他类型会被自动转换为字符串) |
键的顺序 | 键值对保持插入时的顺序 | ES6后: String键按创建顺序;Symbol 键无固定顺序。 ES6前: 顺序无保证,因引擎而异。 |
大小获取 | 通过 size 属性轻松获取:map.size |
需要手动计算:Object.keys(obj).length |
迭代 | 可直接迭代 (是 Iterable 对象),支持 for...of ,直接提供 .keys() , .values() , .entries() 方法 |
默认不可直接迭代 。需要先获取键数组(如Object.keys(obj) ),或使用 for...in (会遍历原型链上的可枚举属性)。 |
默认属性 | 纯净,不包含任何默认的键值对 | 有原型 ,可能包含从原型链继承来的属性(如toString , constructor ),可能干扰键的访问。 |
性能 | 在频繁增删键值对的场景下性能更优 | 在频繁增删键值对的场景下性能稍差(但具体场景需测试) |
序列化 | 默认没有原生的JSON序列化支持 | 完美支持 JSON.stringify 和 JSON.parse |
详细解释与示例
1. 键的类型 (Key Type)
这是最根本的区别。
-
Object 的键只能是 String 或 Symbol 。如果你使用一个非字符串的键(如一个对象),它会被自动通过
.toString()
方法转换为字符串。javascriptconst obj = {}; const key = { id: 1 }; obj[key] = 'value'; // key 被转换为字符串 "[object Object]" console.log(obj); // { "[object Object]": "value" } // 现在你无法再用 key 对象来访问这个值,因为访问时又会转换为相同的字符串
-
Map 的键可以是任何数据类型 ,包括对象、函数、NaN等。它使用"SameValueZero"算法来判断键的相等性(类似于
===
,但认为NaN === NaN
)。javascriptconst map = new Map(); const key = { id: 1 }; map.set(key, 'value'); map.set(NaN, 'This is NaN'); map.set(() => {}, 'A function as key'); console.log(map.get(key)); // 'value' console.log(map.get(NaN)); // 'This is NaN' // 键是唯一的,不会发生转换
2. 顺序 (Order)
- Map 会严格按照键值对的插入顺序来记录和迭代它们。
- Object 的顺序在 ES6 之前没有明确定义。从 ES6 开始,规范规定了:
- String 类型的键会按照创建的顺序记录。
- Symbol 类型的键则没有固定的顺序。
- 但为了兼容性,最好不要依赖 Object 的属性顺序。
3. 迭代 (Iteration)
-
Map 是一个可迭代对象(实现了
Symbol.iterator
),这意味着你可以直接用它来进行for...of
循环,或者使用展开运算符...
将其转换为数组。javascriptconst map = new Map([['a', 1], ['b', 2]]); for (const [key, value] of map) { console.log(key, value); } // Output: a 1, b 2 console.log([...map]); // [ ['a', 1], ['b', 2] ]
-
Object 默认不可直接迭代。你需要使用
Object.keys()
,Object.values()
, 或Object.entries()
先获取一个数组,然后再迭代这个数组。javascriptconst obj = { a: 1, b: 2 }; for (const key of Object.keys(obj)) { console.log(key, obj[key]); }
4. 大小 (Size)
-
Map 的大小可以通过
size
属性轻松、高效地获取。javascriptconsole.log(map.size); // 2
-
Object 的大小需要通过
Object.keys(obj).length
来计算,这在性能敏感的场景下可能稍慢。javascriptconsole.log(Object.keys(obj).length); // 2
5. 原型和默认键 (Prototype and Default Keys)
-
Object 可能受到原型链上的属性污染。如果你不小心,可能会覆盖或访问到继承来的方法(如
hasOwnProperty
)。你需要使用obj.hasOwnProperty('key')
来检查一个属性是否是自身的。javascriptconst obj = {}; console.log('toString' in obj); // true,因为从原型链继承而来
-
Map 是"纯净"的,它只包含你显式放入的键值对,没有任何默认的键,因此更安全。
javascriptconst map = new Map(); console.log(map.has('toString')); // false
6. 序列化和解析 (Serialization and Parsing)
-
Object 可以无缝地与 JSON 格式相互转换。
javascriptconst obj = { a: 1 }; const str = JSON.stringify(obj); // '{"a":1}' const newObj = JSON.parse(str); // { a: 1 }
-
Map 默认无法被
JSON.stringify
正确处理,会序列化为一个空对象{}
。你需要自己编写转换逻辑。javascriptconst map = new Map([['a', 1]]); const str = JSON.stringify(map); // '{}' // 转换 Map -> JSON const mapToJson = (map) => JSON.stringify([...map]); // 转换 JSON -> Map const jsonToMap = (jsonStr) => new Map(JSON.parse(jsonStr));
何时使用 Map vs Object?
使用 Map 的场景:
- 键的类型未知或多样:当你需要使用非字符串/符号作为键时(如 DOM 节点、对象等)。
- 需要维护插入顺序:并且需要依赖这个顺序进行迭代或其他操作。
- 频繁地添加和删除键值对 :
Map
在频繁更新的场景下性能优化得更好。 - 需要避免与原型上的键产生冲突:需要一个纯净的键值对集合时。
使用 Object 的场景:
- 处理 JSON 数据:需要频繁地进行序列化和反序列化。
- 结构是固定的 :你需要一个"实体"来描述某个事物,其属性和方法在开发时就是已知的(例如,
user = { name: 'John', age: 30 }
)。 - 需要用到"方法" :对象可以包含函数(方法),而 Map 的值虽然也可以是函数,但语法上不如对象的方法直观(
obj.method()
vsmap.get('method')()
)。 - 使用简单的字符串键 :并且不需要
Map
提供的那些高级特性(如顺序、任意键类型等)。
总结
如果你需要... | 那么选择... |
---|---|
存储任意类型的键 | Map |
保证键值对的插入顺序 | Map |
频繁地添加/删除元素 | Map |
纯净的、无原型干扰的集合 | Map |
简单的结构,已知的字符串键 | Object |
与 JSON 无缝交互 | Object |
包含方法(函数) | Object |
在现代 JavaScript 开发中,当你的需求更偏向于一个纯粹的"键值对集合"或"哈希映射"时,Map
通常是比 Object
更优的选择。