JavaScript >> Javascript 文檔 >  >> Tags >> map

理解 JavaScript 中的 Map 和 Set

這篇文章最初是為 DigitalOcean 寫的。

在 JavaScript 中,開發人員經常花費大量時間來決定要使用的正確數據結構。這是因為選擇正確的數據結構可以使以後更容易操作該數據,從而節省時間並使代碼更易於理解。用於存儲數據集合的兩種主要數據結構是對象和數組(一種對象)。開發人員使用對象來存儲鍵/值對和數組來存儲索引列表。然而,為了給開發者更多的靈活性,ECMAScript 2015 規範引入了兩種新的可迭代對像類型:Maps,它是鍵/值對的有序集合,以及 Sets,它是唯一值的集合。

在本文中,您將了解 Map 和 Set 對象,它們與對象和數組的相似或不同之處,它們可用的屬性和方法,以及一些實際用途的示例。

地圖

Map 是鍵/值對的集合,可以使用任何數據類型作為鍵,並且可以維護其條目的順序。 Maps 具有 Objects(唯一的鍵/值對集合)和 Arrays(有序集合)的元素,但在概念上更類似於 Objects。這是因為,雖然條目的大小和順序像 Array 一樣被保留,但條目本身是像 Objects 一樣的鍵/值對。

地圖可以用 new Map() 初始化 語法:

const map = new Map()

這給了我們一個空地圖:

Map(0) {}

向地圖添加值

您可以使用 set() 向地圖添加值 方法。第一個參數是鍵,第二個參數是值。

下面將三個鍵/值對添加到 map

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

在這裡,我們開始了解 Maps 如何同時具有 Objects 和 Arrays 的元素。像數組一樣,我們有一個零索引的集合,我們還可以看到默認情況下 Map 中有多少項。地圖使用 => 將鍵/值對錶示為 key => value 的語法 :

Map(3)
0: {"firstName" => "Luke"}
1: {"lastName" => "Skywalker"}
2: {"occupation" => "Jedi Knight"}

這個例子看起來類似於帶有基於字符串的鍵的常規對象,但我們可以使用任何數據類型作為 Map 的鍵。

除了在 Map 上手動設置值之外,我們還可以使用值初始化 Map。我們使用一個包含兩個元素的數組來做到這一點,每個元素都是鍵/值對,如下所示:

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

使用以下語法,我們可以重新創建相同的 Map:

const map = new Map([
  ['firstName', 'Luke'],
  ['lastName', 'Skywalker'],
  ['occupation', 'Jedi Knight'],
])

順便提一下,這個語法和調用 Object.entries() 的結果是一樣的 在一個對像上。這提供了一種現成的方式將 Object 轉換為 Map,如下代碼塊所示:

const luke = {
  firstName: 'Luke',
  lastName: 'Skywalker',
  occupation: 'Jedi Knight',
}

const map = new Map(Object.entries(luke))

或者,您可以使用一行代碼將 Map 轉回 Object 或 Array。

下面將 Map 轉換為 Object:

const obj = Object.fromEntries(map)

這將導致 obj 的以下值 :

{firstName: "Luke", lastName: "Skywalker", occupation: "Jedi Knight"}

現在,讓我們將 Map 轉換為 Array:

const arr = Array.from(map)

這將導致 arr 的以下數組 :

[ ['firstName', 'Luke'],
  ['lastName', 'Skywalker'],
  ['occupation', 'Jedi Knight'] ]

地圖鍵

Maps 接受任何數據類型作為鍵,並且不允許重複鍵值。我們可以通過創建一個映射並使用非字符串值作為鍵,以及將兩個值設置為同一個鍵來證明這一點。

首先,讓我們用非字符串鍵初始化一個映射:

const map = new Map()

map.set('1', 'String one')
map.set(1, 'This will be overwritten')
map.set(1, 'Number one')
map.set(true, 'A Boolean')

此示例將覆蓋 1 的第一個鍵 與後續的,它將處理 '1' 字符串和 1 數字作為唯一鍵:

0: {"1" => "String one"}
1: {1 => "Number one"}
2: {true => "A Boolean"}

儘管人們普遍認為常規的 JavaScript 對像已經可以將數字、布爾值和其他原始數據類型作為鍵來處理,但實際上並非如此,因為對象會將所有鍵都更改為字符串。

例如,使用數字鍵初始化對象並比較數字 1 的值 鍵和字符串化的 "1" 關鍵:

// Initialize an object with a numerical key
const obj = { 1: 'One' }

// The key is actually a string
obj[1] === obj['1'] // true

這就是為什麼如果您嘗試使用 Object 作為鍵,它會打印出字符串 object Object 而是。

舉個例子,創建一個Object,然後用它作為另一個Object的key:

// Create an object
const objAsKey = { foo: 'bar' }

// Use this object as the key of another object
const obj = {
  [objAsKey]: 'What will happen?',
}

這將產生以下結果:

{[object Object]: "What will happen?"}

這不是地圖的情況。嘗試創建一個 Object 並將其設置為 Map 的鍵:

// Create an object
const objAsKey = { foo: 'bar' }

const map = new Map()

// Set this object as the key of a Map
map.set(objAsKey, 'What will happen?')

key Map 元素現在是我們創建的對象。

key: {foo: "bar"}
value: "What will happen?"

使用 Object 或 Array 作為鍵需要注意一件重要的事情:Map 使用對 Object 的引用來比較相等性,而不是 Object 的字面值。在 JavaScript {} === {} 返回 false ,因為這兩個 Object 不是相同的兩個 Object,儘管具有相同的(空)值。

這意味著添加兩個具有相同值的唯一對象將創建一個包含兩個條目的 Map:

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

這將產生以下結果:

Map(2) {{…} => "One", {…} => "Two"}

但是兩次使用相同的 Object 引用將創建一個包含一個條目的 Map。

// Add the same exact object twice as keys to a Map
const obj = {}

map.set(obj, 'One')
map.set(obj, 'Two')

這將導致以下結果:

Map(1) {{…} => "Two"}

第二個set() 正在更新與第一個完全相同的鍵,因此我們最終得到一個只有一個值的 Map。

從地圖中獲取和刪除項目

使用對象的缺點之一是難以枚舉它們,或者使用所有鍵或值。相比之下,Map 結構具有許多內置屬性,可以更直接地處理其元素。

我們可以初始化一個新的 Map 來演示以下方法和屬性:delete() , has() , get() , 和 size .

// Initialize a new Map
const map = new Map([
  ['animal', 'otter'],
  ['shape', 'triangle'],
  ['city', 'New York'],
  ['country', 'Bulgaria'],
])

使用 has() 檢查地圖中是否存在項目的方法。 has() 將返回一個布爾值。

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

使用 get() 通過鍵檢索值的方法。

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

Maps 相對於 Objects 的一個特別好處是您可以隨時找到 Map 的大小,就像使用數組一樣。您可以使用 size 獲取地圖中的項目數 財產。這比將 Object 轉換為 Array 來查找長度所需的步驟更少。

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

使用 delete() 通過鍵從 Map 中刪除項目的方法。該方法將返回一個布爾值——true 如果一個項目存在並被刪除,並且 false 如果它不匹配任何項目。

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

這將產生以下地圖:

Map(3) {"animal" => "otter", "shape" => "triangle", "country" => "Bulgaria"}

最後,可以使用 map.clear() 清除 Map 的所有值 .

// Empty a Map
map.clear()

這將產生:

Map(0) {}

地圖的鍵、值和條目

對象可以使用 Object 的屬性檢索鍵、值和條目 構造函數。另一方面,Map 具有原型方法,允許我們直接獲取 Map 實例的鍵、值和條目。

keys() , values() , 和 entries() 方法都返回一個 MapIterator ,它類似於一個數組,你可以使用 for...of 循環遍歷這些值。

這是另一個 Map 示例,我們可以使用它來演示這些方法。

const map = new Map([
  [1970, 'bell bottoms'],
  [1980, 'leg warmers'],
  [1990, 'flannel'],
])

keys() 方法返回鍵:

map.keys()
MapIterator {1970, 1980, 1990}

values() 方法返回值:

map.values()
MapIterator {"bell bottoms", "leg warmers", "flannel"}

entries() 方法返回一個鍵/值對數組:

map.entries()
MapIterator {1970 => "bell bottoms", 1980 => "leg warmers", 1990 => "flannel"}

使用地圖迭代

地圖有一個內置的 forEach 方法,類似於數組,用於內置迭代。但是,它們迭代的內容有些不同。地圖 forEach 的回調 遍歷 value , key , 和 map 本身,而 Array 版本遍歷 item , index , 和 array 自己。

// Map
Map.prototype.forEach((value, key, map) = () => {}

// Array
Array.prototype.forEach((item, index, array) = () => {}

這是地圖相對於對象的一大優勢,因為對象需要使用 keys() 進行轉換 , values() , 或 entries() ,並且沒有一種簡單的方法可以在不轉換對象的情況下檢索它的屬性。

為了演示這一點,讓我們遍歷我們的 Map 並將鍵/值對記錄到控制台:

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

這將給出:

1970: bell bottoms
1980: leg warmers
1990: flannel

由於 for...of 循環遍歷 Map 和 Array 等可迭代對象,我們可以通過解構 Map 項數組得到完全相同的結果:

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

地圖屬性和方法

下表列出了 Map 的屬性和方法供快速參考:

屬性/方法 說明 返回
set(key, value) 將鍵/值對附加到 Map Map 對象
delete(key) 按鍵從 Map 中刪除鍵/值對 布爾值
get(key) 按鍵返回值 價值
has(key) 按鍵檢查 Map 中元素是否存在 布爾值
clear() 從地圖中刪除所有項目 不適用
keys() 返回 Map 中的所有鍵 MapIterator 對象
values() 返回 Map 中的所有值 MapIterator 對象
entries() 將 Map 中的所有鍵和值返回為 [key, value] MapIterator 對象
forEach() 按插入順序遍歷 Map 不適用
size 返回地圖中的項目數 數字

何時使用地圖

總結一下,Maps 與 Objects 的相似之處在於它們持有鍵/值對,但 Maps 相對於對像有幾個優點:

  • 尺寸 - 地圖有一個 size 屬性,而對像沒有內置方法來檢索其大小。
  • 迭代 - 地圖是可直接迭代的,而對象則不是。
  • 靈活性 - 映射可以有任何數據類型(原始或對象)作為值的鍵,而對像只能有字符串。
  • 已訂購 - 地圖保留其插入順序,而對像沒有保證順序。

由於這些因素,地圖是需要考慮的強大數據結構。但是,Objects 也有一些重要的優勢:

  • JSON - 對象與 JSON.parse() 完美結合 和 JSON.stringify() ,這是處理 JSON 的兩個基本函數,這是許多 REST API 處理的常見數據格式。
  • 使用單個元素 - 使用Object中的已知值,可以直接用key訪問,不需要使用方法,比如Map的get() .

此列表將幫助您確定 Map 或 Object 是否適合您的用例。

設置

Set 是唯一值的集合。與 Map 不同,Set 在概念上更類似於 Array 而不是 Object,因為它是值列表而不是鍵/值對。但是,Set 並不是 Arrays 的替代品,而是為處理重複數據提供額外支持的補充。

您可以使用 new Set() 初始化 Sets 語法。

const set = new Set()

這給了我們一個空集:

Set(0) {}

可以使用 add() 將項目添加到 Set 方法。 (不要與 set() 混淆 Map 可用的方法,雖然它們是相似的。)

// Add items to a Set
set.add('Beethoven')
set.add('Mozart')
set.add('Chopin')

由於 Set 只能包含唯一值,因此任何添加已存在值的嘗試都將被忽略。

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

您還可以使用 Array 值初始化 Sets。如果數組中存在重複值,則將它們從 Set 中移除。

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

反之,只需一行代碼,就可以將 Set 轉換為 Array:

const arr = [...set]
(3) ["Beethoven", "Mozart", "Chopin"]

Set 有許多與 Map 相同的方法和屬性,包括 delete() , has() , clear() , 和 size .

// Delete an item
set.delete('Beethoven') // true

// Check for the existence of an item
set.has('Beethoven') // false

// Clear a Set
set.clear()

// Check the size of a Set
set.size // 0

請注意,Set 無法通過鍵或索引訪問值,例如 Map.get(key)arr[index] .

集合的鍵、值和條目

Map 和 Set 都有 keys() , values() , 和 entries() 返回迭代器的方法。然而,雖然這些方法中的每一個在 Map 中都有不同的用途,但 Set 沒有鍵,因此鍵是值的別名。這意味著 keys()values() 都將返回相同的迭代器和 entries() 將返回該值兩次。只使用 values() 最有意義 使用 Set,因為其他兩種方法的存在是為了與 Map 保持一致和交叉兼容。

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

使用集合迭代

和 Map 一樣,Set 有一個內置的 forEach() 方法。由於Sets沒有key,forEach()的第一個和第二個參數 回調返回相同的值,因此除了與 Map 的兼容性之外,它沒有用例。 forEach()的參數 是 (value, key, set) .

forEach()for...of 可以在 Set 上使用。首先我們來看forEach() 迭代:

const set = new Set(['hi', 'hello', 'good day'])

// Iterate a Set with forEach
set.forEach((value) => console.log(value))

然後我們就可以寫出for...of 版本:

// Iterate a Set with for...of
for (const value of set) {
  console.log(value)
}

這兩種策略都會產生以下結果:

hi
hello
good day

設置屬性和方法

下表為快速參考的Set屬性和方法列表:

屬性/方法 說明 返回
add(value) 將新項目添加到 Set 中 Set 對象
delete(value) 從 Set 中移除指定項 布爾值
has() 檢查集合中是否存在項目 布爾值
clear() 從集合中刪除所有項目 不適用
keys() 返回 Set 中的所有值(與 values() 相同 ) SetIterator 對象
values() 返回 Set 中的所有值(與 keys() 相同 ) SetIterator 對象
entries() 將 Set 中的所有值返回為 [value, value] SetIterator 對象
forEach() 按插入順序遍歷集合 不適用
size 返回集合中的項目數 數字

何時使用集合

Set 是對 JavaScript 工具包的有用補充,尤其適用於處理數據中的重複值。

在一行中,我們可以從具有重複值的 Array 中創建一個沒有重複值的新 Array。

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

這將給出:

(3) [1, 2, 3]

Set 可用於查找兩組數據之間的並集、交集和差異。但是,由於 sort(),數組在對數據進行額外操作方面比 Sets 具有顯著優勢 , map() , filter() , 和 reduce() 方法,以及與 JSON 的直接兼容性 方法。

結論

在本文中,您了解到 Map 是有序鍵/值對的集合,而 Set 是唯一值的集合。這兩種數據結構都為 JavaScript 添加了額外的功能並簡化了常見任務,例如分別查找鍵/值對集合的長度和從數據集中刪除重複項。另一方面,對象和數組傳統上用於 JavaScript 中的數據存儲和操作,並且與 JSON 直接兼容,這繼續使它們成為最重要的數據結構,尤其是在使用 REST API 時。 Maps 和 Sets 主要用於支持對象和數組的數據結構。

如果您想了解有關 JavaScript 的更多信息,請查看我們的如何在 JavaScript 中編碼系列的主頁,或瀏覽我們的如何在 Node.js 中編碼系列以獲取有關後端開發的文章。


Tutorial JavaScript 教程
  1. 如何計算選定的文件

  2. 如何識別哪個 Div 正在顯示滾動條

  3. 如何在 Nuxt Js 項目中設置 Jest 測試

  4. 我們可以從關於 Ember.js 的 +50000 條推文中學到什麼

  5. 使用 ReactJS 和 Firebase 構建博客網站

  6. 基本運費計算器[關閉]

  7. 如何使用 JavaScript 獲取光標下的單詞?

  1. ECMAScript 2015 (ES6) 綜述

  2. Javascript:{this} 的四個規則。

  3. 使用內置或 VS Code 調試器調試 Node.js 應用程序

  4. 簡單的 .npmrc 管理

  5. Chrome 擴展程序:端口錯誤:無法建立連接。接收端不存在。

  6. 你見過的高級開發人員最糟糕的代碼是什麼?

  7. React.memo() 入門

  1. 通過可視化測試保護您的組件庫

  2. 5+ 在線加載 AJAX 微調器生成器工具

  3. 使用 Redux 工具包

  4. ReactJS 虛擬 DOM 和協調 - 像我五歲一樣解釋