← JavaScript异常处理 没有下一篇了 →

JavaScript Map

原创 2026-03-23 JavaScript 已有人查阅

深入理解 JavaScript Map:不仅仅是对象的替代品

在JavaScript的世界里,我们处理数据时,最常用的方式莫过于使用对象(Object)来存储键值对。但随着前端应用逻辑日益复杂,对象的一些固有特性开始显现出局限性。比如,它的键(key)只能是字符串或Symbol类型,这在某些场景下会带来不便。

这时,Map 就登场了。它是ES6引入的一种新的类型,专门用来存储键值对。从名字就能看出,它就像一张“地图”,能够精准地将一个键映射到一个值。在我这几年的开发中,Map给我的感觉就是,它让“键值对”这件事变得纯粹和强大。

什么是 Map?

简单来说,Map 对象就是一个键值对的。它的出现,很大程度上是为了弥补对象在处理复杂键时的不足。你可以通过 new Map() 来创建一个空的Map,也可以在创建时传入一个包含键值对数组(或其他可迭代对象)的 iterable 参数来进行初始化。

语法

new Map([iterable])
  • iterable:可选参数。它应该是一个数组或者其他可迭代对象,其内部的元素必须是键值对形式的数组(例如 [[key1, value1], [key2, value2]])。

Map 的核心特性:你必须知道的几点

如果你之前主要使用对象,那么Map的这几个特性可能会让你眼前一亮:

  1. 键的唯一性:一个Map对象中,不可能存在两个相同的键。如果你用相同的键多次添加,后面的值会覆盖前面的。这保证了映射关系的确定性。

  2. 值的可重复性:值与键不同,Map允许不同的键映射到相同的值。

  3. 键的任意性:这是Map最引人注目的特性。Map的键可以是任何数据类型——不限于字符串,可以是数字、布尔值,甚至是对象、函数。这为数据建模提供了巨大的灵活性。

  4. 有序性:Map会记住键值对的插入顺序。当你遍历它时,元素会按照它们被添加进Map的顺序呈现。

Map 实战:核心方法与使用示例

理论说再多,不如上手一试。下面我们通过一些具体的代码示例,来看看Map是如何工作的。我们把所有示例都放在一个假想的“代码号”学习环境中。

1. 创建与初始化

最直接的创建方式,就是创建一个空的Map,然后逐步添加数据。

// 代码号学习笔记:创建一个空的Map
let myFirstMap = new Map();
console.log(myFirstMap); // 输出: Map(0) {}

也可以在创建时就完成初始化。假设我们想记录三位作者的用户名和他们的文章数量。

// 代码号学习笔记:带初始值的Map
let authorStats = new Map([
    ['alan_writes', 42],
    ['js_master', 108],
    ['code_noob', 7]
]);

console.log(authorStats);
// 输出: Map(3) { 'alan_writes' => 42, 'js_master' => 108, 'code_noob' => 7 }
2. 添加与更新元素:set() 方法

set() 方法用于向Map中添加或更新一个键值对。如果键不存在,就添加;如果键已存在,就更新其值。

// 代码号学习笔记:使用set()添加或更新
let userRoles = new Map();

// 添加新用户
userRoles.set('alice', 'admin');
userRoles.set('bob', 'editor');

console.log(userRoles); // 输出: Map(2) { 'alice' => 'admin', 'bob' => 'editor' }

// 更新已有用户alice的角色
userRoles.set('alice', 'super_admin');
console.log(userRoles); // 输出: Map(2) { 'alice' => 'super_admin', 'bob' => 'editor' }

// 注意,set()方法返回的是当前Map对象,所以支持链式调用
userRoles.set('carol', 'viewer').set('dave', 'contributor');
console.log(userRoles); 
// 输出: Map(4) { 'alice' => 'super_admin', 'bob' => 'editor', 'carol' => 'viewer', 'dave' => 'contributor' }
3. 获取元素:get() 方法

通过键来获取对应的值。这是Map最核心的操作之一。

// 代码号学习笔记:使用get()获取值
console.log(userRoles.get('alice')); // 输出: super_admin
console.log(userRoles.get('bob'));   // 输出: editor

// 如果获取一个不存在的键,会返回 undefined
console.log(userRoles.get('eve'));   // 输出: undefined
4. 检查元素是否存在:has() 方法

在开发中,我们经常需要判断一个键是否存在于Map中,has() 方法就是为此而生。

// 代码号学习笔记:使用has()检查键是否存在
if (userRoles.has('carol')) {
    console.log(`Carol的角色是: ${userRoles.get('carol')}`);
} else {
    console.log('Carol不在系统中');
}
// 输出: Carol的角色是: viewer
5. 删除元素:delete() 方法

当你不再需要某个键值对时,可以通过 delete() 方法将其移除。它会返回一个布尔值,表示删除是否成功。

// 代码号学习笔记:使用delete()删除元素
let wasDeleted = userRoles.delete('dave');
console.log(wasDeleted); // 输出: true
console.log(userRoles); 
// 输出: Map(3) { 'alice' => 'super_admin', 'bob' => 'editor', 'carol' => 'viewer' }

// 尝试删除一个不存在的键
wasDeleted = userRoles.delete('nonexistent');
console.log(wasDeleted); // 输出: false
6. 清空Map:clear() 方法

如果想一键删除Map中的所有元素,使用 clear() 是最快捷的方式。

// 代码号学习笔记:使用clear()清空整个Map
userRoles.clear();
console.log(userRoles); // 输出: Map(0) {}
7. 遍历Map:keys()values()entries()forEach()

遍历Map是另一个常见操作。Map提供了几种迭代器方法,非常灵活。

  • keys(): 返回一个包含所有键的迭代器。

  • values(): 返回一个包含所有值的迭代器。

  • entries(): 返回一个包含所有 [key, value] 对的迭代器。这也是 for...of 循环遍历Map时的默认行为。

  • forEach(): 为每个键值对执行一次提供的函数。

// 重新创建一个示例Map
let fruitsStock = new Map([
    ['apple', 50],
    ['banana', 30],
    ['orange', 20]
]);

// 使用for...of和entries()遍历键值对(推荐方式)
console.log('--- 遍历键值对 ---');
for (let [fruit, quantity] of fruitsStock) {
    console.log(`${fruit}: ${quantity}`);
}
// 输出:
// apple: 50
// banana: 30
// orange: 20

// 使用keys()遍历所有键
console.log('--- 遍历所有键 ---');
for (let fruit of fruitsStock.keys()) {
    console.log(fruit);
}
// 输出: apple, banana, orange

// 使用values()遍历所有值
console.log('--- 遍历所有值 ---');
for (let quantity of fruitsStock.values()) {
    console.log(quantity);
}
// 输出: 50, 30, 20

// 使用forEach(),注意回调函数参数顺序是 (value, key, map)
console.log('--- 使用forEach遍历 ---');
fruitsStock.forEach((value, key) => {
    console.log(`商品: ${key}, 库存: ${value}`);
});
// 输出:
// 商品: apple, 库存: 50
// 商品: banana, 库存: 30
// 商品: orange, 库存: 20

经验之谈:什么时候用Map,什么时候用Object?

这是很多开发者都会困惑的问题。我个人认为,选择Map还是Object,并不是技术问题,更多是取决于使用场景的“语境”。

  • 当键是动态的,或不是字符串时,果断选择Map。 如果你需要一个对象作为键来关联某些数据(比如,用DOM元素作为键来存储其状态),Map是唯一合理的选择。普通对象无法做到这一点,它会将传入的对象键自动转换为字符串[object Object],导致数据错乱。

  • 当需要频繁进行增删操作时,Map的性能通常更好。 在大量数据的场景下,Map的setdelete操作比对象属性的添加和删除有更好的优化。

  • 当需要保持顺序时,Map是自然的选择。 虽然现在浏览器已经会按照创建顺序来遍历对象的字符串键,但无法保证数字键和Symbol键的顺序,而Map的迭代顺序是严格遵循插入顺序的。

  • 当数据结构仅仅是简单的、静态的“记录”时,Object更合适。 比如,你有一个已知的、固定的配置项,键都是字符串,且不需要频繁增删,那么对象字面量({ key: value })的写法更简洁,也更符合直觉。并且,对象可以直接利用JSON进行序列化,而Map则需要手动处理。

Map提供了更统一、更纯粹的键值对接口,而对象则更贴近“记录”或“实体”的概念。理解它们的区别,能帮助你在编码时做出更合适的选择。

本节课程知识要点

  1. 键的灵活性:Map允许任何类型的值作为键,包括对象和函数,这是它相较于传统对象的显著优势。

  2. 顺序保证:Map会严格按照键值对的插入顺序进行迭代,这在需要维护元素顺序的场景下非常重要。

  3. 直接迭代:Map实例本身是可迭代的,可以直接用于for...of循环,默认迭代器就是entries(),这使得遍历变得非常方便。

  4. 大小获取:通过size属性(例如 map.size)可以轻松获取Map中元素的数量,而对象则需要通过Object.keys(obj).length来间接获取,效率更低。

  5. 性能考量:在需要频繁添加和删除键值对的场景下,Map的内部实现通常比对象更优。

← JavaScript异常处理 没有下一篇了 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号