在 JavaScript 中, `Map` 和 `Object` 都可用于存储键值对,但设计目标、特性和适用场景有显著差异。
在 JavaScript 中,Map 和 Object 都可用于存储键值对,但设计目标、特性和适用场景有显著差异。以下是核心区别的详细解析,结合使用场景帮助理解:
一、核心区别对比表
| 特性 | Object | Map |
|---|---|---|
| 键的类型 | 仅支持 String/Symbol(其他类型会自动转为字符串) | 支持任意类型(String、Number、Object、Function 等,按引用比较对象键) |
| 键的唯一性 | 字符串键自动去重(后定义覆盖前定义) | 所有类型键严格唯一(基于 SameValueZero 算法:NaN 视为相等,对象按引用区分) |
| 键的顺序 | ES6 前无明确顺序;ES6 后按“插入顺序”,但数字键会被排序(转为字符串后按数值大小排序) | 严格保留插入顺序(遍历顺序 = 插入顺序,无自动排序) |
| 迭代性 | 需手动遍历(for...in、Object.keys() 等),且会遍历原型链上的可枚举属性 | 可直接迭代(for...of、entries()/keys()/values()),仅遍历自身属性 |
| 长度/大小 | 无内置属性,需通过 Object.keys(obj).length 计算 | 内置 size 属性,直接获取键值对数量(高效) |
| 原型链 | 继承自 Object.prototype(存在原型污染风险,如 obj.toString 是内置方法) | 原型为 null(无继承属性,更纯粹的键值对集合) |
| 增删改查效率 | 普通场景高效,但频繁增删(尤其是大量数据)时性能略逊 | 频繁增删、查找大量数据时性能更稳定(底层为哈希表优化) |
| 序列化 | 支持 JSON.stringify()(仅序列化字符串键的可枚举属性,忽略 Symbol 键和原型属性) | 不支持直接序列化(JSON.stringify(map) 结果为 {}),需手动转换 |
| 额外功能 | 支持计算属性、原型方法(如 hasOwnProperty) | 内置实用方法(has(key)、delete(key)、clear()),支持键为对象 |
二、关键区别详解
1. 键的类型与唯一性
-
Object 的键限制:
- 本质上只有
String和Symbol两种类型的键。 - 若传入其他类型(如
Number、Object),会自动转为字符串:const obj = {}; obj[123] = '数字键'; obj[{ id: 1 }] = '对象键'; console.log(Object.keys(obj)); // ["123", "[object Object]"](对象键被转为字符串) - 字符串键会自动去重(后定义的覆盖前定义的)。
- 本质上只有
-
Map 的键灵活性:
- 支持任意类型的键(
Number、Object、Function、NaN等),且不转换类型:const map = new Map(); const key1 = 123; const key2 = { id: 1 }; map.set(key1, '数字键'); map.set(key2, '对象键'); map.set(NaN, 'NaN 值');console.log(map.has(key1)); // true console.log(map.has({ id: 1 })); // false(对象按引用比较,新对象≠key2) console.log(map.has(NaN)); // true(NaN 视为相等) - 键的唯一性基于
SameValueZero算法(比===更宽松,NaN === NaN为false,但 Map 中视为同一键)。
- 支持任意类型的键(
2. 键的顺序与迭代
-
Object 的顺序问题:
- ES6 之前无明确顺序;ES6 之后,遍历顺序为:先排数字键(按数值大小),再按插入顺序排字符串键和
Symbol键。 - 迭代需手动处理,且可能遍历到原型链上的属性(需用
hasOwnProperty过滤):const obj = { b: 2, 1: 1, a: 3 }; console.log(Object.keys(obj)); // ["1", "b", "a"](数字键优先排序)// 遍历自身属性 for (const key in obj) {if (obj.hasOwnProperty(key)) {console.log(key, obj[key]);} }
- ES6 之前无明确顺序;ES6 之后,遍历顺序为:先排数字键(按数值大小),再按插入顺序排字符串键和
-
Map 的有序迭代:
- 严格保留插入顺序,遍历顺序与插入顺序一致。
- 可直接通过
for...of迭代,或使用entries()(默认)、keys()、values()方法,无需过滤原型属性:const map = new Map(); map.set('b', 2); map.set(1, 1); map.set('a', 3);for (const [key, value] of map) {console.log(key, value); // 依次输出:b 2 → 1 1 → a 3(插入顺序) }
3. 大小获取与性能
-
Object 的大小计算:
- 无内置
size属性,需通过Object.keys(obj).length或Object.entries(obj).length计算(需遍历所有键,效率低)。
- 无内置
-
Map 的大小获取:
- 内置
size属性,直接返回键值对数量(O(1) 时间复杂度,高效):const map = new Map([['a', 1], ['b', 2]]); console.log(map.size); // 2(直接获取)
- 内置
-
性能差异:
- 少量数据、简单键(字符串)场景:
Object和Map性能接近。 - 大量数据、频繁增删改查:
Map性能更优(底层哈希表优化,增删操作不会触发原型链查找)。 Object频繁增删时,可能因哈希表重构导致性能波动。
- 少量数据、简单键(字符串)场景:
4. 原型链与污染风险
-
Object 的原型问题:
- 所有
Object实例继承自Object.prototype,可能存在原型污染:const obj = {}; console.log(obj.toString); // 函数(继承自 Object.prototype) obj.toString = '被覆盖'; // 污染原型方法(不推荐) - 遍历的时需注意过滤原型属性(避免遍历到
toString、hasOwnProperty等内置方法)。
- 所有
-
Map 的纯净性:
Map的原型为null,无继承属性,不存在原型污染风险:const map = new Map(); console.log(map.toString); // undefined(无继承的 toString 方法)
5. 序列化与使用场景
-
Object 的序列化:
- 支持
JSON.stringify(),但仅序列化字符串键的可枚举属性,忽略Symbol键、原型属性和不可枚举属性:const obj = { a: 1, [Symbol('b')]: 2 }; console.log(JSON.stringify(obj)); // {"a":1}(Symbol 键被忽略)
- 支持
-
Map 的序列化:
- 不支持直接序列化,需手动转换为数组或对象:
const map = new Map([['a', 1], [Symbol('b'), 2]]); console.log(JSON.stringify(map)); // {}(直接序列化失败)// 手动转换为数组(可序列化) const arr = Array.from(map.entries()); console.log(JSON.stringify(arr)); // [["a",1]](Symbol 键仍被忽略)
- 不支持直接序列化,需手动转换为数组或对象:
三、适用场景选择
优先用 Object 的场景:
- 存储简单的键值对(键为字符串/
Symbol,且无需复杂操作)。 - 需要 JSON 序列化/反序列化(如接口数据传输)。
- 场景简单,追求代码简洁(如配置对象)。
优先用 Map 的场景:
- 键为非字符串类型(如
Object、Number、Function)。 - 需要严格保证键的插入顺序。
- 频繁增删改查,或数据量较大(追求性能稳定)。
- 需频繁获取键值对数量(
size属性高效)。 - 避免原型污染,需要纯粹的键值对集合。
四、补充:Map 与 Object 的相互转换
1. Object → Map
const obj = { a: 1, b: 2 };
const map = new Map(Object.entries(obj)); // 基于 Object.entries 转换
2. Map → Object
const map = new Map([['a', 1], ['b', 2]]);
const obj = Object.fromEntries(map.entries()); // 基于 Map.entries 转换(仅支持字符串/数字键)
总结
Object 是 JavaScript 最基础的数据结构,适合简单场景和 JSON 序列化;Map 是 ES6 新增的专门用于键值对存储的结构,在键类型灵活性、顺序、性能等方面更有优势,适合复杂场景。选择时需根据键的类型、顺序需求、操作频率、序列化需求综合判断。
