为什么 JavaScript 中 Map 比 Object 更好

原文: 《Why Is JavaScript's Map Better Than Object?》

作者:Hui

JavaScript 提供了 Map 和 Object 来存储键值对,但是 Map 在许多场景下具有显著的优势。

1. Key 类型的灵活性

1.1 键的范围

Object:

对象键 只能是字符串或符号 。其他类型(例如对象、函数和数字)会 自动转换为字符串

javascript 复制代码
const obj = {};
const key = { id: 1 };
obj[key] = 'value'; // Key is converted to "[object Object]"
console.log(obj);    // { "[object Object]": "value" }

Map:

Map 的键可以是 任何类型,包括对象、函数和 NaN:

javascript 复制代码
Copyconst map = new Map();
const key = { id: 1 };
map.set(key, 'value');  // Key retains its original type
console.log(map.get(key)); // "value"

1.2 处理特殊键

使用 NaN 作为键:

javascript 复制代码
Copyconst map = new Map();
map.set(NaN, 'Not a Number');
console.log(map.get(NaN)); // "Not a Number"
const obj = {};
obj[NaN] = 'Not a Number';
console.log(obj[NaN]); // "Not a Number", but internally converted to the string "NaN"

Map 可以正确识别 NaN 作为唯一键,而 Object 会将其转换为字符串。

2. 内置方法和性能

2.1 内置方法

Map 提供了更直观的 API:

javascript 复制代码
Copymap.set(key, value);  // Add a key-value pair
map.get(key);         // Retrieve a value
map.has(key);         // Check if a key exists
map.delete(key);      // Remove a key-value pair
map.clear();          // Remove all entries
map.size;             // Get the number of entries (no need for Object.keys(obj).length)

相比之下,Object 需要手动处理:

javascript 复制代码
Copyobj[key] = value;      // Add a property
obj[key];              // Retrieve a value
delete obj[key];       // Remove a property
Object.keys(obj).length; // Get the number of properties

2.2 迭代效率

Map 支持直接迭代:

javascript 复制代码
Copymap.forEach((value, key) => { /* ... */ });
for (const [key, value] of map) { /* ... */ }

相反,Object 在迭代之前需要进行转换:

javascript 复制代码
CopyObject.keys(obj).forEach(key => { /* ... */ });
Object.values(obj).forEach(value => { /* ... */ });
Object.entries(obj).forEach(([key, value]) => { /* ... */ });

2.3 性能比较

  • 频繁的增删改查操作:Map 针对频繁的键值插入和删除操作做了优化。
  • 处理大型数据集:Map 通常在内存使用和访问速度方面表现更好,尤其是动态生成的键。

3. 保留插入顺序

Map 严格维护键值对的插入顺序,非常适合顺序很重要的场景:

javascript 复制代码
Copyconst map = new Map();
map.set('a', 1);
map.set('b', 2);
console.log([...map]); // [['a', 1], ['b', 2]]

对于对象,ES6+ 保证以下顺序:

  • 数字键按升序排序。
  • 字符串键维持插入顺序。
  • 符号键保持插入顺序。

但是,依赖对象键顺序可能会导致兼容性问题,尤其是在较旧的 JavaScript 引擎中。

4. 避免原型污染

对象容易受到原型链污染:

javascript 复制代码
Copyconst obj = {};
console.log(obj.constructor); // Outputs Object constructor
obj.hasOwnProperty('key');    // Can be overridden

Map 独立于原型链:

javascript 复制代码
Copyconst map = new Map();
console.log(map.constructor); // Outputs Map constructor
map.set('hasOwnProperty', 'safe'); // Safe to use

5. 推荐使用场景

场景 推荐的数据结构 原因
复杂键类型(对象、函数) Map 支持任何键类型
频繁的键值插入和删除 Map Map 性能更强
保持插入顺序 Map 保证插入顺序
快速键值计数和检索 Map 插入键值对数量和大小计算更快
避免原型污染 Map 不受原型链的干扰
只需要基础字符串键名的简单静态数据 Object 语法简洁
对象需要 JSON 序列化 Object Map 不能直接序列化

6. 代码示例

6.1 词频统计

使用Map:

javascript 复制代码
Copyconst text = "apple banana apple orange";
const wordCount = new Map();
text.split(' ').forEach(word => {
  wordCount.set(word, (wordCount.get(word) || 0) + 1);
});
console.log(wordCount.get('apple')); // 2

使用 Object:

javascript 复制代码
Copyconst text = "apple banana apple orange";
const wordCount = {};
text.split(' ').forEach(word => {
  wordCount[word] = (wordCount[word] || 0) + 1;
});
console.log(wordCount.apple); // 2

6.2 使用对象作为键

使用 Map 的正确实现:

javascript 复制代码
Copyconst user1 = { id: 1 };
const user2 = { id: 2 };
const permissions = new Map();
permissions.set(user1, ['read']);
permissions.set(user2, ['write']);
console.log(permissions.get(user1)); // ['read']

使用 Object 会失败:

javascript 复制代码
Copyconst user1 = { id: 1 };
const user2 = { id: 2 };
const permissions = {};
permissions[user1] = ['read'];  // Key converted to "[object Object]"
permissions[user2] = ['write']; // Overwrites previous key
console.log(permissions[user1]); // ['write']

7. 结论

在以下情况下使用 Map:

  • 使用动态或复杂的键类型
  • 执行频繁插入或删除
  • 保留插入顺序
  • 快速检索已插入条目数量
  • 避免原型链干扰

在以下情况下使用对象:

  • 存储简单、静态的数据
  • 需要 JSON 序列化
  • 更喜欢使用更简洁的语法来定义键值对

在现代 JavaScript 开发中选择正确的数据结构可以显著提高代码的可维护性和性能。

相关推荐
默默地写代码12 分钟前
微信小程序 新版canvas绘制名片
前端·javascript·微信小程序
BillKu16 分钟前
Element Plus 对话框 el-dialog 和 抽屉 el-drawer 的使用注意项(使用 div 包裹)
javascript·vue.js·elementui
layman052818 分钟前
Vue 中的配置代理
前端·javascript·vue.js
BillKu24 分钟前
Vue3 + TypeScript 中 hook 优化记录
开发语言·javascript·typescript
未来之窗软件服务27 分钟前
jquery 赋值时不触发change事件解决——仙盟创梦IDE
前端·javascript·jquery·仙盟创梦ide·东方仙盟
AnyaPapa32 分钟前
【解决方案】Vue 常见问题大全
前端·javascript·vue.js
浩宇软件开发1 小时前
JavaScript 数组常用方法 find, findIndex, filter, map, flatMap, some
前端·javascript·vue.js
永生辉皇2 小时前
JS红宝书笔记 8.3 继承
开发语言·javascript·笔记
前端Hardy2 小时前
前端性能飞跃!9大高级API实战指南,80%的开发者只知其三
前端·javascript
永生辉皇2 小时前
JS红宝书笔记 8.2 创建对象
javascript·笔记·原型模式