这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情
- 对象,存储带有键的数据的集合。
- 数组,存储有序集合。
但这还不足以应对现实情况。这就是为什么存在 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
在默认情况下使用的就是这个。
例如:
let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
]);
// 遍历所有的键(vegetables)
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // cucumber, tomatoes, onion
}
// 遍历所有的值(amounts)
for (let amount of recipeMap.values()) {
alert(amount); // 500, 350, 50
}
// 遍历所有的实体 [key, value]
for (let entry of recipeMap) { // 与 recipeMap.entries() 相同
alert(entry); // cucumber,500 (and so on)
}
使用插入顺序
迭代的顺序与插入值的顺序相同。与普通的 Object
不同,Map
保留了此顺序。
除此之外,Map
有内建的 forEach
方法,与 Array
类似:
// 对每个键值对 (key, value) 运行 forEach 函数
recipeMap.forEach( (value, key, map) => {
alert(`${key}: ${value}`); // cucumber: 500 etc
});
Object.entries:从对象创建 Map
当创建一个 Map
后,我们可以传入一个带有键值对的数组(或其它可迭代对象)来进行初始化,如下所示:
// 键值对 [key, value] 数组
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
]);
alert( map.get('1') ); // str1
如果我们想从一个已有的普通对象(plain object)来创建一个 Map
,那么我们可以使用内建方法 Object.entries(obj),该方法返回对象的键/值对数组