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 更适合表示具有方法和逻辑的实体对象。

相关推荐
蜡台19 分钟前
Uniapp H5Builderx 预览Html 显示404问题解决
前端·uni-app
We་ct25 分钟前
LeetCode 190. 颠倒二进制位:两种解法详解
前端·算法·leetcode·typescript
踩着两条虫35 分钟前
AI驱动的Vue3应用开发平台深入探究(二十五):API与参考之Renderer API 参考
前端·javascript·vue.js·人工智能·低代码·前端框架·ai编程
信创DevOps先锋43 分钟前
本土化突围:Gitee如何重新定义企业级项目管理工具价值
前端·gitee·jquery
圣光SG1 小时前
Java类与对象及面向对象基础核心详细笔记
java·前端·数据库
Jinuss1 小时前
源码分析之React中的useImperativeHandle
开发语言·前端·javascript
ZC跨境爬虫1 小时前
CSS核心知识点与定位实战全解析(结合Playwright爬虫案例)
前端·css·爬虫
Jinuss1 小时前
源码分析之React中的forwardRef解读
前端·javascript·react.js
mengsi551 小时前
Antigravity IDE 在浏览器上 verify 成功但本地 IDE 没反应 “开启Tun依然无济于事” —— 解决方案
前端·ide·chrome·antigravity