为什么 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 开发中选择正确的数据结构可以显著提高代码的可维护性和性能。

相关推荐
开开心心就好30 分钟前
批量提取PDF中的图片,直接导出原图
前端·javascript·支持向量机·智能手机·pdf·html·启发式算法
默_笙5 小时前
🏛 给 AI 配一间办公室:Harness Engineering 六大模块与它的实现
前端·javascript
linux_cfan6 小时前
videojs v10 源代码系列解读:14 · 谓词守卫:在运行时安全地调用能力
前端·javascript·音视频
天若有情6738 小时前
【纯前端小工具】公历生日转农历,批量查询每年农历生日对应的公历日期(GitHub Pages在线直接用)
前端·javascript·github pages·农历转换·lunisolar·网页小工具
林语琛9 小时前
我写的 switch…break 被 Babel 偷偷吞了
前端·javascript·babel
liuchangng9 小时前
Jev 模型研究:从生成式大模型到决策式模型——System One、RLCD 校准与采用边界
java·javascript·人工智能·python·深度学习
我命由我1234511 小时前
CSS - CSS 媒体查询 orientation
前端·javascript·css·html·css3·html5·js
志尊宝13 小时前
Vue3 零基础每日笔记(048):嵌套路由与命名视图——后台管理系统布局雏形
前端·javascript·vue.js·笔记·html5
@tangguo12314 小时前
npm 和 yarn 配置说明
前端·javascript·npm·node.js·yarn
志尊宝14 小时前
Vue3 零基础每日笔记(052):路由常见坑一次排雷——404、刷新丢参数、部署白屏
前端·javascript·vue.js·笔记·html5