前端JavaScript:Object和Map及其区别是什么?

0 阅读8分钟

在 JavaScript 中,ObjectMap 都是用于存储键值对的数据结构。长期以来,开发者们习惯使用普通对象来处理映射关系,但随着 ES6 的到来,Map 的出现彻底改变了这一局面。你是否曾疑惑过,为什么明明对象也能存键值对,还要引入 Map?它们之间到底有什么区别?什么时候该用 Map,什么时候该用 Object?本文将从底层原理到实战应用,带你彻底搞懂这两个数据结构。

一、基础认知:从设计初衷说起

1.1 传统的 Object:为结构化数据而生

普通对象(Plain Object)是 JavaScript 中最基础的数据结构之一,它的设计初衷是用来表示一个 “实体” 或 “结构化数据”。比如:

const user = {
  name: '张三',
  age: 25,
  city: '北京'
};

在这个例子中,user 代表了一个用户实体,它的键是固定的字符串,值是对应的属性。这种场景下,Object 非常直观,我们可以通过 . 操作符快速访问属性。

1.2 现代的 Map:为通用映射而生

Map 是 ES6 引入的新数据结构,它的设计目标是成为一个通用的键值对映射容器。它不再局限于 “实体” 的概念,而是更像一个字典,允许你将任意类型的值映射到另一个值,无论键是什么类型。

const userMap = new Map();
userMap.set('name', '张三');
userMap.set({ id: 1 }, '用户详情'); // 直接用对象作为键

二、核心差异:8 个维度的全面对比

为了让你直观地看到两者的区别,我们先来看一张完整的特性对比表:

图 1:Map 与 Object 核心特性对比

接下来,我们深入解析这些差异。

2.1 键的类型:突破限制的灵活性

这是 Map 最核心的优势。

  • Object:键只能是 字符串Symbol 类型。如果你尝试使用其他类型,JavaScript 会自动调用 toString() 方法将其转换为字符串。
  • Map:键可以是 任意类型,包括对象、函数、数组、数字、布尔值,甚至 NaN
// Object 的隐式类型转换
const obj = {};
const key1 = { id: 1 };
const key2 = { name: 'test' };

obj[key1] = '这是第一个对象';
obj[key2] = '这是第二个对象';

console.log(obj[key1]); 
// 输出:"这是第二个对象"!因为 key1.toString() 和 key2.toString() 都是 "[object Object]"

而在 Map 中,这完全不是问题:

const map = new Map();
const key1 = { id: 1 };
const key2 = { name: 'test' };

map.set(key1, '这是第一个对象');
map.set(key2, '这是第二个对象');

console.log(map.get(key1)); // 输出:"这是第一个对象"
console.log(map.get(key2)); // 输出:"这是第二个对象"

这意味着,你可以直接将 DOM 元素、函数实例作为键,来存储它们的关联数据,而无需手动生成唯一 ID。

2.2 键的顺序:严格的插入顺序

很多人以为 Object 的键是无序的,其实在 ES6 之后,Object 也开始保留插入顺序了,但它有一个致命的例外:数字键会被优先排序

const obj = {};
obj['b'] = 2;
obj['1'] = 1;
obj['a'] = 3;
obj['2'] = 4;

console.log(Object.keys(obj)); 
// 输出:["1", "2", "b", "a"]
// 数字键被自动排到了前面,完全打乱了插入顺序!

而 Map 则严格保证了插入顺序,没有任何例外:

const map = new Map();
map.set('b', 2);
map.set('1', 1);
map.set('a', 3);
map.set('2', 4);

console.log(Array.from(map.keys())); 
// 输出:["b", "1", "a", "2"]
// 完美遵循了我们的插入顺序

这对于日志记录、有序缓存等时序敏感的场景至关重要。

2.3 大小获取:O (1) vs O (n)

获取键值对的数量,两者的效率天差地别。

  • Object:你必须手动遍历所有键来计算长度,这是一个 O (n) 的操作。

    •     const size = Object.keys(obj).length;
      
  • Map:内置了 size 属性,直接返回大小,这是一个 O (1) 的操作,无需遍历。

    •     const size = map.size;
      

2.4 迭代能力:原生的遍历支持

  • Object:它本身不是可迭代对象(Iterable),你无法直接使用 for...of 遍历它。必须先通过 Object.keys()Object.entries() 等方法转换为数组。
  • Map:它原生实现了迭代器协议,你可以直接遍历它,而且默认就是遍历键值对。
// Map 直接遍历
for (const [key, value] of map) {
  console.log(key, value);
}

// Object 必须转换
for (const [key, value] of Object.entries(obj)) {
  console.log(key, value);
}

2.5 原型链污染:安全的隔离

普通对象默认继承了 Object.prototype,这意味着它自带了 toStringhasOwnProperty 等默认属性。如果你不小心用这些名字作为键,就会发生冲突,甚至引发原型链污染攻击。

const obj = {};
console.log(obj.toString); // 输出:[Function: toString],这是原型上的方法

而 Map 从一开始就是一张白纸,它没有原型,完全不存在这个问题:

const map = new Map();
console.log(map.has('toString')); // 输出:false

三、性能深度剖析:谁更快?

很多人都听说过 Map 性能更好,但具体好在哪里?我们来看一下基于 V8 引擎的实测数据。

图 2:10 万次操作下的性能对比(单位:毫秒)

3.1 底层实现的差异

  • Object:V8 引擎为了优化属性访问,引入了 “隐藏类(Hidden Class)” 的机制。当你创建一个对象并添加固定的属性时,V8 会为它生成一个隐藏类,属性访问会被优化为直接的内存偏移,速度极快。但是,一旦你频繁地添加和删除属性,隐藏类就会不断地被重建和重排,这会带来巨大的性能开销,甚至会降级到 “字典模式”。
  • Map:它的底层是基于哈希表(Hash Table)实现的。哈希表天生就为频繁的增删查改做了优化,插入、删除、查找的平均时间复杂度都是 O (1)。无论你怎么操作,它的性能都非常稳定。

3.2 关键发现

从测试数据中我们可以看到:

  1. 删除操作:Map 比 Object 快了近 3 倍!这是因为 Object 删除属性会触发隐藏类的重排,而 Map 的哈希表删除只是调整指针。
  2. 插入操作:Map 也有明显优势,特别是在动态数据场景下。
  3. 查找操作:两者差距不大,Object 因为隐藏类的优化,在小数据量下甚至略快。
  4. 内存占用:存储 10 万条数据时,Map 比 Object 节省了约 38% 的内存。

四、实战应用:什么时候用哪个?

了解了原理,我们来看看实际开发中该如何选择。

4.1 优先使用 Map 的场景

当你遇到以下情况时,Map 绝对是更好的选择:

1. 键不是简单的字符串

比如你需要用对象、DOM 元素作为键。

// 存储 DOM 元素的关联数据
const elementData = new Map();
const button = document.querySelector('#btn');

elementData.set(button, { clickCount: 0 });

button.addEventListener('click', () => {
  const data = elementData.get(button);
  data.clickCount++;
});

2. 需要频繁增删键值对

比如缓存系统、高频更新的状态。

// 防止重复请求
const pendingRequests = new Map();

function requestInterceptor(config) {
  const key = generateRequestKey(config);
  if (pendingRequests.has(key)) {
    // 取消之前的请求
    pendingRequests.get(key).cancel();
  }
  // 存储新的请求
  pendingRequests.set(key, cancelToken);
}

3. 需要有序的键值对

比如日志记录、有序的配置列表。

4. 需要频繁查询大小

比如你需要经常知道当前缓存里有多少条数据。

4.2 优先使用 Object 的场景

当然,Object 并没有被淘汰,在这些场景下,它依然是首选:

1. 存储静态的结构化数据

比如用户信息、配置项,这些数据的键是已知的、固定的字符串。

const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  debug: true
};

这种场景下,Object 的 . 语法访问属性比 map.get() 更直观,而且 V8 的隐藏类优化能发挥到极致。

2. 需要 JSON 序列化

Object 天生支持 JSON.stringify(),而 Map 不支持,需要手动转换。

JSON.stringify(user); // 正常工作
JSON.stringify(userMap); // 输出:{},无法直接序列化

3. 简单的一次性数据处理

如果只是临时存几个简单的键值对,用完就扔,用字面量 {} 创建对象比 new Map() 更快捷。

五、面试高频考点

这部分是面试中的常客,你需要掌握:

  1. 问:Map 和 Object 的区别是什么? 答:从键的类型、顺序、大小获取、迭代、原型链、性能这几个维度回答即可。
  2. 问:Object 的键顺序是怎样的? 答:ES6 之后,Object 会先把整数键按升序排,然后字符串和 Symbol 键按插入顺序排。而 Map 是严格的插入顺序。
  3. 问:为什么 Map 在频繁增删时性能更好? 答:因为 Object 底层是隐藏类,频繁增删会导致隐藏类重排;而 Map 是哈希表,增删查改都是 O (1) 的稳定操作。

六、总结

Map 和 Object 并不是谁取代谁的关系,它们是互补的。

  • Object 更像一个 “数据模型”,适合存储结构固定、键为字符串的静态数据,它支持 JSON 序列化,语法直观。
  • Map 更像一个 “数据容器”,适合处理动态的、复杂的映射关系,它支持任意键、有序性、高效的增删操作。

在现代前端开发中,随着应用复杂度的提升,Map 的使用场景越来越多。学会根据业务场景灵活选择,才能写出更高效、更健壮的代码。

参考资料

[1] MDN Web Docs. 带键的集合 [EB/OL]. developer.mozilla.org/zh-CN/docs/…, 2025. [2] zqmgx13291. JavaScript Map 数据结构:原理、实践与性能优化 [EB/OL]. CSDN 博客,2025. [3] 前端小木屋. Object 与 Map 的区别有哪些?[EB/OL]. 稀土掘金,2025. [4] Pu_Nine_9. 深入理解 ES6 Map 数据结构:从理论到实战应用 [EB/OL]. CSDN 博客,2026. [5] 软件求生。你以为你会用 Map? 这些细节 90% 的人都忽略了 [EB/OL]. 今日头条,2026.