到目前为止,我们已经学习了以下复杂的数据结构
- 对象用于存储键控集合。
- 数组用于存储有序集合。
但这对于实际生活来说还不够。这就是 Map
和 Set
也存在的原因。
Map
Map 是一个键控数据项集合,就像一个 Object
。但主要区别在于 Map
允许任何类型的键。
方法和属性是
new Map()
– 创建映射。map.set(key, value)
– 根据键存储值。map.get(key)
– 通过键返回该值,如果key
不存在于 map 中,则返回undefined
。map.has(key)
– 如果key
存在,则返回true
,否则返回false
。map.delete(key)
– 通过键移除元素(键/值对)。map.clear()
– 从 map 中移除所有内容。map.size
– 返回当前元素数量。
例如
let
map =
new
Map
(
)
;
map.
set
(
'1'
,
'str1'
)
;
// a string key
map.
set
(
1
,
'num1'
)
;
// a numeric key
map.
set
(
true
,
'bool1'
)
;
// a boolean key
// remember the regular Object? it would convert keys to string
// Map keeps the type, so these two are different:
alert
(
map.
get
(
1
)
)
;
// 'num1'
alert
(
map.
get
(
'1'
)
)
;
// 'str1'
alert
(
map.
size )
;
// 3
正如我们所见,与对象不同,键不会转换为字符串。任何类型的键都是可能的。
map[key]
不是使用 Map
的正确方法虽然 map[key]
也可行,例如我们可以设置 map[key] = 2
,但这将 map
视为一个普通的 JavaScript 对象,因此它暗示了所有相应的限制(仅字符串/符号键等)。
所以我们应该使用 map
方法:set
、get
等。
Map 还可以使用对象作为键。
例如
let
john =
{
name:
"John"
}
;
// for every user, let's store their visits count
let
visitsCountMap =
new
Map
(
)
;
// john is the key for the 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 =
{
}
;
// try to use an object
visitsCountObj[
ben]
=
234
;
// try to use ben object as the key
visitsCountObj[
john]
=
123
;
// try to use john object as the key, ben object will get replaced
// That's what got written!
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
,有 3 种方法
map.keys()
– 返回一个可迭代的键map.values()
– 返回一个可迭代的值map.entries()
– 返回一个可迭代的条目[key, value]
,它在for..of
中默认使用。
例如
let
recipeMap =
new
Map
(
[
[
'cucumber'
,
500
]
,
[
'tomatoes'
,
350
]
,
[
'onion'
,
50
]
]
)
;
// iterate over keys (vegetables)
for
(
let
vegetable of
recipeMap.
keys
(
)
)
{
alert
(
vegetable)
;
// cucumber, tomatoes, onion
}
// iterate over values (amounts)
for
(
let
amount of
recipeMap.
values
(
)
)
{
alert
(
amount)
;
// 500, 350, 50
}
// iterate over [key, value] entries
for
(
let
entry of
recipeMap)
{
// the same as of recipeMap.entries()
alert
(
entry)
;
// cucumber,500 (and so on)
}
迭代的顺序与插入值时的顺序相同。Map
保留此顺序,这与常规 Object
不同。
除此之外,Map
还具有一个内置的 forEach
方法,类似于 Array
// runs the function for each (key, value) pair
recipeMap.
forEach
(
(
value,
key,
map
)
=>
{
alert
(
`
${
key}
:
${
value}
`
)
;
// cucumber: 500 etc
}
)
;
Object.entries:从 Object 映射
创建 Map
时,我们可以传递一个包含键/值对的数组(或其他可迭代对象)进行初始化,如下所示
// array of [key, value] pairs
let
map =
new
Map
(
[
[
'1'
,
'str1'
]
,
[
1
,
'num1'
]
,
[
true
,
'bool1'
]
]
)
;
alert
(
map.
get
(
'1'
)
)
;
// str1
如果我们有一个普通对象,并且我们希望从中创建一个 Map
,那么我们可以使用内置方法 Object.entries(obj),它返回一个键/值对数组,该数组的格式完全符合对象。
因此,我们可以像这样从对象创建一个映射
let
obj =
{
name:
"John"
,
age:
30
}
;
let
map =
new
Map
(
Object.
entries
(
obj)
)
;
alert
(
map.
get
(
'name'
)
)
;
// John
此处,Object.entries
返回键/值对数组:[ ["name","John"], ["age", 30] ]
。这就是 Map
所需的。
Object.fromEntries:从 Map 映射
我们刚刚看到如何使用 Object.entries(obj)
从普通对象创建 Map
。
有一个 Object.fromEntries
方法可以执行相反的操作:给定一个 [key, value]
对数组,它会从中创建一个对象
let
prices =
Object.
fromEntries
(
[
[
'banana'
,
1
]
,
[
'orange'
,
2
]
,
[
'meat'
,
4
]
]
)
;
// now prices = { banana: 1, orange: 2, meat: 4 }
alert
(
prices.
orange)
;
// 2
我们可以使用 Object.fromEntries
从 Map
获取一个普通对象。
例如,我们将数据存储在 Map
中,但我们需要将其传递给期望普通对象的第三方代码。
我们开始
let
map =
new
Map
(
)
;
map.
set
(
'banana'
,
1
)
;
map.
set
(
'orange'
,
2
)
;
map.
set
(
'meat'
,
4
)
;
let
obj =
Object.
fromEntries
(
map.
entries
(
)
)
;
// make a plain object (*)
// done!
// obj = { banana: 1, orange: 2, meat: 4 }
alert
(
obj.
orange)
;
// 2
调用 map.entries()
返回一个键/值对的可迭代对象,其格式完全符合 Object.fromEntries
。
我们还可以缩短行 (*)
let
obj =
Object.
fromEntries
(
map)
;
// omit .entries()
这是相同的,因为 Object.fromEntries
期望一个可迭代对象作为参数。不一定是数组。并且 map
的标准迭代返回与 map.entries()
相同的键/值对。因此,我们得到一个与 map
具有相同键/值的普通对象。
Set
Set
是一种特殊的类型集合——“值集合”(没有键),其中每个值只能出现一次。
它的主要方法是
new Set([iterable])
– 创建集合,如果提供了iterable
对象(通常是一个数组),则将值从中复制到集合中。set.add(value)
– 添加一个值,返回集合本身。set.delete(value)
– 删除该值,如果value
在调用时存在,则返回true
,否则返回false
。set.has(value)
– 如果值存在于集合中,则返回true
,否则返回false
。set.clear()
– 从集合中移除所有内容。set.size
– 是元素计数。
主要特点是重复调用 set.add(value)
,其中值相同,不会执行任何操作。这就是每个值仅在 Set
中出现一次的原因。
例如,我们有访客来访,我们希望记住每个人。但重复访问不应导致重复项。访客必须仅被“计数”一次。
Set
正好适合这种情况
let
set =
new
Set
(
)
;
let
john =
{
name:
"John"
}
;
let
pete =
{
name:
"Pete"
}
;
let
mary =
{
name:
"Mary"
}
;
// visits, some users come multiple times
set.
add
(
john)
;
set.
add
(
pete)
;
set.
add
(
mary)
;
set.
add
(
john)
;
set.
add
(
mary)
;
// set keeps only unique values
alert
(
set.
size )
;
// 3
for
(
let
user of
set)
{
alert
(
user.
name)
;
// John (then Pete and Mary)
}
Set
的替代方案可能是用户数组,以及使用 arr.find 在每次插入时检查重复项的代码。但性能会差很多,因为此方法会遍历整个数组并检查每个元素。Set
在内部针对唯一性检查进行了更好的优化。
遍历 Set
我们可以使用 for..of
或 forEach
循环遍历集合
let
set =
new
Set
(
[
"oranges"
,
"apples"
,
"bananas"
]
)
;
for
(
let
value of
set)
alert
(
value)
;
// the same with forEach:
set.
forEach
(
(
value,
valueAgain,
set
)
=>
{
alert
(
value)
;
}
)
;
请注意有趣的事情。在 forEach
中传递的回调函数有 3 个参数:一个 value
,然后是相同的值 valueAgain
,然后是目标对象。事实上,相同的值在参数中出现两次。
这是为了与 Map
兼容,其中传递给 forEach
的回调函数有三个参数。看起来有点奇怪,这是肯定的。但在某些情况下,这可能有助于轻松地用 Set
替换 Map
,反之亦然。
Map
为迭代器提供的相同方法也受支持
set.keys()
– 返回值的迭代对象,set.values()
– 与set.keys()
相同,为了与Map
兼容,set.entries()
– 返回条目[value, value]
的迭代对象,存在是为了与Map
兼容。
总结
Map
– 是键控值集合。
方法和属性
new Map([iterable])
– 创建映射,其中包含用于初始化的[key,value]
对的可选iterable
(例如数组)。map.set(key, value)
– 按键存储值,返回映射本身。map.get(key)
– 通过键返回该值,如果key
不存在于 map 中,则返回undefined
。map.has(key)
– 如果key
存在,则返回true
,否则返回false
。map.delete(key)
– 按键删除元素,如果调用时key
存在,则返回true
,否则返回false
。map.clear()
– 从 map 中移除所有内容。map.size
– 返回当前元素数量。
与普通Object
的区别
- 任何键,对象可以是键。
- 其他便捷方法,
size
属性。
Set
– 是一个唯一值集合。
方法和属性
new Set([iterable])
– 创建集合,带有可选的iterable
(例如数组)值,用于初始化。set.add(value)
– 添加一个值(如果value
存在,则不执行任何操作),返回集合本身。set.delete(value)
– 删除该值,如果value
在调用时存在,则返回true
,否则返回false
。set.has(value)
– 如果值存在于集合中,则返回true
,否则返回false
。set.clear()
– 从集合中移除所有内容。set.size
– 是元素计数。
对Map
和Set
的迭代始终按插入顺序进行,因此我们不能说这些集合是无序的,但我们不能重新排列元素或直接按其编号获取元素。