了解 JavaScript 中的 Map 和 Set 对象

作者选择了 开放式互联网 / 自由言论基金作为 写给捐赠计划的一部分接受捐款。

在JavaScript中,开发人员经常花费大量的时间来决定使用正确的数据结构,这是因为选择正确的数据结构可以使数据更容易操纵,节省时间,并使代码更容易理解。用于存储数据集的两个主要数据结构是 ObjectsArrays(对象类型)。

在本文中,您将讨论地图和设置对象,这些对象与对象和阵列有什么相似或不同,它们可用的属性和方法,以及一些实际用途的示例。

地图

地图是一组钥匙/值对,可以使用任何 数据类型作为密钥,并可以保持其条目的顺序。地图有对象(一个独特的钥匙/值对集)和数组(一个顺序集)的元素,但在概念上更类似于对象。

地图可以用新地图()语法初始化:

1const map = new Map()

这给了我们一个空的地图:

1[secondary_label Output]
2Map(0) {}

向地图添加值

您可以使用 set() 方法将值添加到地图中,第一个参数将是密钥,第二个参数将是值。

下面将三个键/值对添加到地图:

1map.set('firstName', 'Luke')
2map.set('lastName', 'Skywalker')
3map.set('occupation', 'Jedi Knight')

在这里,我们开始看到地图如何具有 Objects 和 Arrays 的元素。 就像一个 Array,我们有一个零索引的集合,我们也可以看到默认情况下地图中有多少元素。

1[secondary_label Output]
2Map(3)
30: {"firstName" => "Luke"}
41: {"lastName" => "Skywalker"}
52: {"occupation" => "Jedi Knight"}

此示例看起来类似于使用基于字符串的常规对象,但我们可以使用任何数据类型作为地图的密钥。

除了在地图上手动设置值外,我们还可以初始化具有值的地图,我们使用包含两个元素的 Array of Arrays 来完成此操作,每个元素都是键/值对,这看起来像这样:

1[ [ 'key1', 'value1'], ['key2', 'value2'] ]

使用以下语法,我们可以重建相同的地图:

1const map = new Map([
2  ['firstName', 'Luke'],
3  ['lastName', 'Skywalker'],
4  ['occupation', 'Jedi Knight'],
5])

<$>[注] **注:**本示例使用 trailing commas,也称为 dangling commas. 这是一个JavaScript格式化做法,在声明数据收集时,系列中的最后一个项目在尾声时有一个字符号。 虽然这种格式化选择可以用于更清晰的 diffs和更容易的代码操纵,无论使用它是否是偏好问题。 有关 trailing commas的更多信息,请参阅此 Trailing Comma 文章从 MDN 网页文件 <$>

顺便说一句,这个语法与在一个对象上调用 Object.entries()的结果相同,这提供了将一个对象转换为地图的准备方式,如下列代码块所示:

1const luke = {
2  firstName: 'Luke',
3  lastName: 'Skywalker',
4  occupation: 'Jedi Knight',
5}
6
7const map = new Map(Object.entries(luke))

或者,您可以用单行代码将地图转换为对象或数组。

以下是将地图转换为对象:

1const obj = Object.fromEntries(map)

这将导致obj的以下值:

1[secondary_label Output]
2{firstName: "Luke", lastName: "Skywalker", occupation: "Jedi Knight"}

现在,让我们将地图转换为 Array:

1const arr = Array.from(map)

这将导致arr的下列数组:

1[secondary_label Output]
2[ ['firstName', 'Luke'],
3  ['lastName', 'Skywalker'],
4  ['occupation', 'Jedi Knight'] ]

地图 钥匙

地图接受任何数据类型作为密钥,不允许重复关键值,我们可以通过创建地图和使用非字符串值作为密钥,以及为同一个密钥设置两个值来证明这一点。

首先,让我们用非字符串键初始化一张地图:

1const map = new Map()
2
3map.set('1', 'String one')
4map.set(1, 'This will be overwritten')
5map.set(1, 'Number one')
6map.set(true, 'A Boolean')

此示例将1的第一个密钥与下一个重叠,并将1字符串和1数字视为唯一的密钥:

1[secondary_label Output]
20: {"1" => "String one"}
31: {1 => "Number one"}
42: {true => "A Boolean"}

虽然人们普遍认为,一个常规的 JavaScript 对象已经可以将数字、booleans 和其他原始数据类型作为密钥处理,但实际上并非如此,因为 Objects 会将所有密钥变成字符串。

举个例子,用数字密钥初始化一个对象,并比较数字1密钥和有序的1密钥的值:

1// Initialize an object with a numerical key
2const obj = { 1: 'One' }
3
4// The key is actually a string
5obj[1] === obj['1']  // true

因此,如果您尝试使用 Object 作为密钥,它会打印object Object字符串。

例如,创建一个对象,然后使用它作为另一个对象的密钥:

1// Create an object
2const objAsKey = { foo: 'bar' }
3
4// Use this object as the key of another object
5const obj = {
6  [objAsKey]: 'What will happen?'
7}

这将产生如下:

1[secondary_label Output]
2{[object Object]: "What will happen?"}

尝试创建一个对象并将其设置为地图的密钥:

1// Create an object
2const objAsKey = { foo: 'bar' }
3
4const map = new Map()
5
6// Set this object as the key of a Map
7map.set(objAsKey, 'What will happen?')

地图元素的钥匙现在是我们创建的对象。

1[secondary_label Output]
2key: {foo: "bar"}
3value: "What will happen?"

要注意使用一个对象或数组作为密钥的一个重要事项:地图使用对象的参考来比较平等,而不是对象的字面值。

这意味着添加两个具有相同值的独特对象将创建一个包含两个条目的地图:

1// Add two unique but similar objects as keys to a Map
2map.set({}, 'One')
3map.set({}, 'Two')

这将产生如下:

1[secondary_label Output]
2Map(2) {{…} => "One", {…} => "Two"}

但是,使用相同的对象引用两次将创建一张地图。

1// Add the same exact object twice as keys to a Map
2const obj = {}
3
4map.set(obj, 'One')
5map.set(obj, 'Two')

这将导致如下:

1[secondary_label Output]
2Map(1) {{…} => "Two"}

第二个set()更新与第一个相同的准确密钥,所以我们最终会得到一个只有一个值的地图。

从地图中获取和删除项目

与 Objects 工作的一个缺点是,很难列出它们,或者使用所有键或值工作,相反,地图结构具有许多内置的属性,使其元素的工作更直接。

我们可以初始化一个新地图来演示以下方法和属性: delete(), has(), get()size

1// Initialize a new Map
2const map = new Map([
3  ['animal', 'otter'],
4  ['shape', 'triangle'],
5  ['city', 'New York'],
6  ['country', 'Bulgaria'],
7])

使用 has() 方法来检查地图中的项目是否存在. has() 将返回 Boolean。

1// Check if a key exists in a Map
2map.has('shark') // false
3map.has('country') // true

使用get()方法以按键获取值。

1// Get an item from a Map
2map.get('animal') // "otter"

地图具有对象的特别好处之一是,您可以随时找到地图的大小,就像您可以使用数组一样。您可以使用大小属性获得地图中的项目数目。

1// Get the count of items in a Map
2map.size // 4

使用删除()方法从地图按键删除一个项目. 该方法将返回一个布尔文 - 如果一个项目存在并且被删除,则返回一个,如果没有匹配任何项目,则返回一个

1// Delete an item from a Map by key
2map.delete('city') // true

这将导致以下地图:

1[secondary_label Output]
2Map(3) {"animal" => "otter", "shape" => "triangle", "country" => "Bulgaria"}

最后, Map 可以用 map.clear() 清除所有值。

1// Empty a Map
2map.clear()

這將會產生:

1[secondary_label Output]
2Map(0) {}

用于地图的密钥、值和输入

对象可以通过使用对象构建器的属性获取密钥、值和条目,而地图则具有原型方法,使我们能够直接获取地图实例的密钥、值和条目。

所有 keys()values()entries() 方法都返回一个 MapIterator,这类似于一个 Array,您可以使用 for...of 通过值循环。

以下是另一个地图的例子,我们可以使用它来展示这些方法:

1const map = new Map([
2  [1970, 'bell bottoms'],
3  [1980, 'leg warmers'],
4  [1990, 'flannel'],
5])

keys()方法返回以下键:

1map.keys()
1[secondary_label Output]
2MapIterator {1970, 1980, 1990}

values() 方法返回值:

1map.values()
1[secondary_label Output]
2MapIterator {"bell bottoms", "leg warmers", "flannel"}

entries() 方法返回一个关键/值对数组:

1map.entries()
1[secondary_label Output]
2MapIterator {1970 => "bell bottoms", 1980 => "leg warmers", 1990 => "flannel"}

使用地图的迭代

地图有一个内置的forEach方法,类似于一个 Array,用于内置的迭代。然而,它们的迭代在某种程度上有所不同。地图的forEach的回调通过关键地图本身进行迭代,而Array版本则通过项目索引数组本身进行迭代。

1// Map 
2Map.prototype.forEach((value, key, map) = () => {})
3
4// Array
5Array.prototype.forEach((item, index, array) = () => {})

这对于地图对对象来说是一个很大的优势,因为对象需要用 keys()values()entries() 进行转换,并且没有一个简单的方法可以检索对象的属性而不进行转换。

为了证明这一点,让我们通过我们的地图重复并将密钥 / 值对登录到控制台:

1// Log the keys and values of the Map with forEach
2map.forEach((value, key) => {
3  console.log(`${key}: ${value}`)
4})

这将给出:

1[secondary_label Output]
21970: bell bottoms
31980: leg warmers
41990: flannel

由于for...of循环在地图和 Array 等迭代元素上迭代,我们可以通过破坏地图元素的数组来获得完全相同的结果:

1// Destructure the key and value out of the Map item
2for (const [key, value] of map) {
3  // Log the keys and values of the Map with for...of
4  console.log(`${key}: ${value}`)
5}

地图属性和方法

下表显示了快速参考的地图属性和方法列表:

======================================================================================================= -------------------------------------------------------------------- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- [`has(key)'(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has)___________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

何时使用地图

总之,地图类似于对象,因为它们具有关键/值对,但地图对对象有几个优势:

  • 大小 - 地图具有大小属性,而对象没有内置的方式来检索其大小
  • Iteration - 地图可以直接迭代,而对象不是
  • 灵活性 - 地图可以将任何数据类型(原始或对象)作为值的密钥,而对象只能有字符串
  • ** Ordered** - 地图保留其插入顺序,而对象没有保证的顺序

由于这些因素,地图是一个强大的数据结构来考虑,但是,对象也有几个重要的优势:

  • JSON - 对象与JSON.parse()JSON.stringify()无缝工作,这两个功能对于与(JSON)(https://andsky.com/tech/tutorials/how-to-work-with-json-in-javascript)工作至关重要,这是许多REST API处理的常见数据格式
  • 使用单个元素 - 在对象中使用已知值,您可以直接使用密钥访问它,而无需使用任何方法,例如地图的get()

此列表将帮助您决定地图或对象是否为您的使用案例提供正确的数据结构。

一组是独特值的集合,与地图不同,一组在概念上比对象更类似于数组,因为它是一个值列表,而不是密钥/值对。

您可以使用新 Set()语法初始化 Sets。

1const set = new Set()

这给了我们一个空的集合:

1[secondary_label Output]
2Set(0) {}

项目可以用add()方法添加到集合中(这不应该与Map中可用的set()方法混淆,尽管它们相似)。

1// Add items to a Set
2set.add('Beethoven')
3set.add('Mozart')
4set.add('Chopin')

由于集只能包含唯一的值,因此将忽略任何尝试添加已存在的值。

1set.add('Chopin') // Set will still contain 3 unique values

<$>[注] :相同的平等比较适用于地图键适用于设置项目. 两个具有相同值但不共享相同参考的对象不会被视为相等。

您还可以使用数组初始化数组,如果数组中有重复值,则将从数组中删除。

1// Initialize a Set from an Array
2const set = new Set(['Beethoven', 'Mozart', 'Chopin', 'Chopin'])
1[secondary_label Output]
2Set(3) {"Beethoven", "Mozart", "Chopin"}

相反,一个集可以用一个代码行转换为一个 Array:

1const arr = [...set]
1[secondary_label Output]
2(3) ["Beethoven", "Mozart", "Chopin"]

Set 具有与 Map 相同的许多方法和属性,包括 delete()has()clear()size

 1// Delete an item
 2set.delete('Beethoven') // true
 3
 4// Check for the existence of an item
 5set.has('Beethoven') // false
 6
 7// Clear a Set
 8set.clear()
 9
10// Check the size of a Set
11set.size // 0

请注意,设置没有方法通过键或索引访问一个值,例如Map.get(key)arr[index]

密钥、值和集的输入

Map 和 Set 都有 keys()values()entries() 方法,返回一个 Iterator. 然而,虽然这些方法在 Map 中都有不同的目的,但 Sets 没有密钥,因此密钥是值的代名词。 这意味着 keys() 和 values() 都将返回相同的 Iterator,而 entries() 将返回值两次。 最有意义的是只使用 values() 与 Set,因为其他两种方法存在于一致性和与 Map 交叉兼容性方面。

1const set = new Set([1, 2, 3])
2// Get the values of a set
3set.values()
1[secondary_label Output]
2SetIterator {1, 2, 3}

Iteration 与 Set

像 Map 一样,Set 具有内置的 forEach() 方法. 由于 Sets 没有键,所以 forEach() 调用返回的第一个和第二个参数返回了相同的值,因此没有与 Map 兼容以外的用例。

forEach()for...of都可以在设置中使用,首先,让我们看看forEach()迭代:

1const set = new Set(['hi', 'hello', 'good day'])
2
3// Iterate a Set with forEach
4set.forEach((value) => console.log(value))

然后我们可以写for...of版本:

1// Iterate a Set with for...of
2for (const value of set) {  
3    console.log(value);
4}

这两种策略将产生如下结果:

1[secondary_label Output]
2hi
3hello
4good day

设定属性和方法

下表显示了快速参考的设置属性和方法列表:

======================================================================================================= -------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

何时使用SET

Set 是您的 JavaScript 工具包的有用补充,特别是用于处理数据中的重复值。

在单个行中,我们可以从具有重复值的 Array 创建一个新的 Array 没有重复值。

1const uniqueArray = [ ...new Set([1, 1, 2, 2, 2, 3])] // (3) [1, 2, 3]

这将给出:

1[secondary_label Output]
2(3) [1, 2, 3]

Set 可用于查找两个数据集之间的联结、交叉和差异,但是,由于sort(),map(),filter(),reduce()方法,以及与JSON方法的直接兼容性,Sets 具有显著的优势。

结论

在这篇文章中,您了解到地图是排序的密钥/值对的集合,而集合是独特值的集合。这两个数据结构都为JavaScript增添了额外的功能,并简化了常见的任务,例如查找密钥/值对集合的长度,并分别从数据集中删除重复项目。另一方面,对象和数组传统上用于JavaScript中的数据存储和操纵,并且与JSON有直接兼容性,这继续使它们成为最基本的数据结构,特别是用于REST API。

如果您想了解更多关于JavaScript的信息,请参阅我们的首页 如何在JavaScript中编码系列,或浏览我们的 如何在Node.js系列中编码关于后端开发的文章。

Published At
Categories with 技术
comments powered by Disqus