map和Object有什么区别

MapObject 都是键值对集合,但在设计和使用上有重要区别:

1. 核心区别对比表

特性 Map Object
键的类型 任意类型(对象、函数、NaN等) String 或 Symbol(其他类型会被转为字符串)
键的顺序 保持插入顺序 ES6 后也保持插入顺序,但有例外
大小获取 .size 属性直接获取 需要计算:Object.keys(obj).length
默认键 无默认键,完全是空的 有原型链,可能包含继承的键
性能 频繁增删时性能更好 频繁增删时性能较差
迭代 直接可迭代(for...of 需要先获取键数组再迭代
序列化 不能直接 JSON 序列化 可直接 JSON 序列化
垃圾回收 强引用(除非用 WeakMap) 强引用

2. 详细对比

键的类型

js 复制代码
// Map:键可以是任意类型
const map = new Map();
const objKey = { id: 1 };
const funcKey = function() {};
const arrKey = [1, 2];
const nanKey = NaN;

map.set(objKey, '对象键');
map.set(funcKey, '函数键');
map.set(arrKey, '数组键');
map.set(nanKey, 'NaN键'); // ✅ NaN 作为键
map.set(null, 'null键');
map.set(undefined, 'undefined键');

// Object:键会被转换为字符串
const obj = {};
obj[objKey] = '对象键'; // 键被转为 "[object Object]"
obj[funcKey] = '函数键'; // 键被转为 "function() {}"
obj[arrKey] = '数组键'; // 键被转为 "1,2"
obj[null] = 'null键'; // 键被转为 "null"
obj[undefined] = 'undefined键'; // 键被转为 "undefined"

顺序保证

js 复制代码
const map = new Map();
map.set('a', 1);
map.set('b', 2);
map.set('0', 3); // 数字字符串键
map.set('1', 4);
map.set('c', 5);

// Map:严格保持插入顺序
console.log([...map.keys()]); // ['a', 'b', '0', '1', 'c']

const obj = {};
obj['a'] = 1;
obj['b'] = 2;
obj['0'] = 3; // 数字字符串键
obj['1'] = 4;
obj['c'] = 5;

// Object:整数属性(可转为数字的字符串)会按数字顺序排列
console.log(Object.keys(obj)); // ['0', '1', 'a', 'b', 'c']

原型链问题

js 复制代码
// Object 有原型链继承
const obj = {};
console.log(obj.toString); // ƒ toString() { [native code] } - 来自原型

// 可能导致意外的键冲突
obj['constructor']; // 可能不是你设置的值,而是继承的
obj['__proto__']; // 特殊属性

// Map 完全没有这个问题
const map = new Map();
map.set('constructor', '这是我设置的构造函数');
map.set('__proto__', '这是我设置的属性');
console.log(map.get('constructor')); // '这是我设置的构造函数'
console.log(map.get('__proto__')); // '这是我设置的属性'

性能对比

js 复制代码
// 频繁增删场景 - Map 性能更好
const map = new Map();
const obj = {};

console.time('Map 添加');
for (let i = 0; i < 100000; i++) {
  map.set(i, i);
}
console.timeEnd('Map 添加'); // 通常更快

console.time('Object 添加');
for (let i = 0; i < 100000; i++) {
  obj[i] = i;
}
console.timeEnd('Object 添加');

// 频繁删除场景 - Map 优势更明显
console.time('Map 删除');
for (let i = 0; i < 100000; i++) {
  map.delete(i);
}
console.timeEnd('Map 删除');

console.time('Object 删除');
for (let i = 0; i < 100000; i++) {
  delete obj[i];
}
console.timeEnd('Object 删除'); // 通常更慢

迭代方式

js 复制代码
const map = new Map([
  ['name', 'Alice'],
  ['age', 25]
]);

const obj = {
  name: 'Alice',
  age: 25
};

// Map:直接可迭代
for (let [key, value] of map) {
  console.log(key, value);
}

// Map 的便捷方法
map.forEach((value, key) => console.log(key, value));

// Object:需要先获取键数组
for (let key in obj) {
  if (obj.hasOwnProperty(key)) { // 需要过滤原型链
    console.log(key, obj[key]);
  }
}

// 或使用 Object 方法
Object.keys(obj).forEach(key => console.log(key, obj[key]));
Object.entries(obj).forEach(([key, value]) => console.log(key, value));

3. 使用场景建议

优先使用 Map 的情况:

js 复制代码
// 1. 键不是字符串或 Symbol
const userRoles = new Map();
userRoles.set(user1, 'admin'); // 对象作为键
userRoles.set(user2, 'editor');

// 2. 频繁增删键值对
const cache = new Map(); // 缓存系统
cache.set(key, value);
cache.delete(key);

// 3. 需要保持严格的插入顺序
const orderedMap = new Map();
orderedMap.set('z', 1);
orderedMap.set('a', 2); // 遍历时保证 z 在 a 之前

// 4. 键值对数量需要频繁获取
if (map.size > 100) { } // 直接获取

// 5. 避免与原型链属性冲突
const safeMap = new Map();
safeMap.set('constructor', '不会冲突');
safeMap.set('__proto__', '安全的');

优先使用 Object 的情况:

js 复制代码
// 1. 简单的键值对,键都是字符串/Symbol
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3
};

// 2. 需要 JSON 序列化/反序列化
const data = { name: 'Alice', age: 25 };
const json = JSON.stringify(data); // 直接支持
const parsed = JSON.parse(json);

// Map 需要转换
const mapData = new Map([['name', 'Alice'], ['age', 25]]);
const mapJson = JSON.stringify([...mapData]); // 需要转为数组
const mapParsed = new Map(JSON.parse(mapJson));

// 3. 需要方法(函数作为值)
const calculator = {
  add(a, b) { return a + b; },
  multiply(a, b) { return a * b; }
};

// 4. 使用 Object 特殊功能
const obj = Object.create(null); // 创建无原型的对象
Object.defineProperty(obj, 'readonly', {
  value: '不可写',
  writable: false
});

4. 互相转换

js 复制代码
// Object → Map
const obj = { a: 1, b: 2 };
const map = new Map(Object.entries(obj));

// Map → Object
const map2 = new Map([['x', 10], ['y', 20]]);
const obj2 = Object.fromEntries(map2);

// Object → Map(处理嵌套)
function objectToMap(obj) {
  const map = new Map();
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      const value = obj[key];
      map.set(key, typeof value === 'object' && value !== null ? 
        objectToMap(value) : value);
    }
  }
  return map;
}

5. 特殊注意事项

js 复制代码
// 1. Map 的相等性判断
const map = new Map();
map.set({}, 'value1');
map.set({}, 'value2'); // 两个空对象是不同的键
console.log(map.size); // 2

// 2. 引用相同的对象是同一个键
const key = { id: 1 };
map.set(key, 'value');
map.set(key, 'new value'); // 更新,不是添加
console.log(map.size); // 3(包含前面的两个空对象)

// 3. NaN 作为键的特殊性
map.set(NaN, 'first');
map.set(NaN, 'second'); // NaN 被视为相同的键
console.log(map.get(NaN)); // 'second'

// 4. 性能权衡
// Object 在已知结构、固定键时,引擎优化更好
// Map 在动态键、频繁增删时表现更好

总结

选择 Map 当:

  • 键的类型多样(对象、函数等)
  • 需要频繁增删键值对
  • 需要保持严格的插入顺序
  • 避免与原型链冲突
  • 需要直接获取大小

选择 Object 当:

  • 键都是字符串/Symbol
  • 需要 JSON 序列化
  • 需要对象方法(函数作为值)
  • 使用 Object 特殊功能(getter/setter、属性描述符)
  • 简单的配置对象或数据传输对象

在现代 JavaScript 开发中,对于纯粹的键值对存储,Map 通常是更好的选择,因为它更安全、更灵活且性能更好。Object 更适合表示具有方法和逻辑的实体对象。

相关推荐
胖鱼罐头1 小时前
JavaScript 数据类型完全指南
前端·javascript
Snack1 小时前
border-radius带来的锯齿问题
前端
代码猎人1 小时前
Set、Map有什么区别
前端
ETA81 小时前
不再是切图仔,router拯救了前端工程师
前端·react.js
毕设源码-朱学姐1 小时前
【开题答辩全过程】以 基于web网络投票系统平台的设计与实现为例,包含答辩的问题和答案
前端
萌狼蓝天1 小时前
[Vue]Tab关闭后,再次使用这个组件时,上次填写的内容依旧显示(路由复用导致组件实例未被销毁)
前端·javascript·vue.js·前端框架·ecmascript
皮坨解解1 小时前
关于领域模型的总结
前端
UIUV1 小时前
React+Zustand实战学习笔记:从基础状态管理到项目实战
前端·react.js·typescript
ETA81 小时前
理解 React 自定义 Hook:不只是“封装”,更是思维方式的转变
前端·react.js