学到现在,我们已经了解了以下复杂的数据结构:
- 对象,存储带有键的数据的集合。
- 数组,存储有序集合。
但这还不足以应对现实情况。这就是为什么存在 Map 和 Set。
Map
Map 是一个带键的数据项的集合,就像一个 Object 一样。 但是它们最大的差别是 Map 允许任何类型的键(key)。
它的方法和属性如下:
new Map()—— 创建 map。map.set(key, value)—— 根据键存储值。map.get(key)—— 根据键来返回值,如果map中不存在对应的key,则返回undefined。map.has(key)—— 如果key存在则返回true,否则返回false。map.delete(key)—— 删除指定键的值。map.clear()—— 清空 map。map.size—— 返回当前元素个数。
举个例子:
let map = new Map();
map.set('1', 'str1'); // 字符串键
map.set(1, 'num1'); // 数字键
map.set(true, 'bool1'); // 布尔值键
// 还记得普通的 Object 吗? 它会将键转化为字符串
// Map 则会保留键的类型,所以下面这两个结果不同:
alert( map.get(1) ); // 'num1'
alert( map.get('1') ); // 'str1'
alert( map.size ); // 3
如我们所见,与对象不同,键不会被转换成字符串。键可以是任何类型。
map[key] 不是使用 Map 的正确方式虽然 map[key] 也有效,例如我们可以设置 map[key] = 2,这样会将 map 视为 JavaScript 的 plain object,因此它暗含了所有相应的限制(仅支持 string/symbol 键等)。
所以我们应该使用 map 方法:set 和 get 等。
Map 还可以使用对象作为键。
例如:
let john = { name: "John" };
// 存储每个用户的来访次数
let visitsCountMap = new Map();
// john 是 Map 中的键
visitsCountMap.set(john, 123);
alert( visitsCountMap.get(john) ); // 123
使用对象作为键是 Map 最值得注意和重要的功能之一。在 Object 中,我们则无法使用对象作为键。在 Object 中使用字符串作为键是可以的,但我们无法使用另一个 Object 作为 Object 中的键。
我们来尝试一下:
let john = { name: "John" };
let ben = { name: "Ben" };
let visitsCountObj = {}; // 尝试使用对象
visitsCountObj[ben] = 234; // 尝试将对象 ben 用作键
visitsCountObj[john] = 123; // 尝试将对象 john 用作键,但我们会发现使用对象 ben 作为键存下的值会被替换掉
// 变成这样了!
alert( visitsCountObj["[object Object]"] ); // 123
因为 visitsCountObj 是一个对象,它会将所有 Object 键例如上面的 john 和 ben 转换为字符串 "[object Object]"。这显然不是我们想要的结果。
Map 是怎么比较键的?Map 使用 SameValueZero 算法来比较键是否相等。它和严格等于 === 差不多,但区别是 NaN 被看成是等于 NaN。所以 NaN 也可以被用作键。
这个算法不能被改变或者自定义。
链式调用
每一次 map.set 调用都会返回 map 本身,所以我们可以进行“链式”调用:
map.set('1', 'str1')
.set(1, 'num1')
.set(true, 'bool1');
Map 迭代
如果要在 map 里使用循环,可以使用以下三个方法:
map.keys()—— 遍历并返回一个包含所有键的可迭代对象,map.values()—— 遍历并返回一个包含所有值的可迭代对象,map.entries()—— 遍历并返回一个包含所有实体[key, value]的可迭代对象,for..of在默认情况下使用的就是这个。
例如: