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 更优的选择。