Tutorial

Знакомство с объектами map и set в JavaScript

Published on March 5, 2020
Русский
Знакомство с объектами map и set в JavaScript

Автор выбрал фонд Open Internet/Free Speech для получения пожертвования в рамках программы Write for DOnations.

В JavaScript разработчики часто тратят много времени на решение того, какую структуру данных следует использовать. Это вызвано тем, что выбор правильной структуры данных упрощает последующее управление этими данными, экономя время и упрощая понимание кода. Двумя преобладающими структурами для хранения коллекций данных являются объекты и массивы (тип объекта). Разработчики используют объекты для хранения пары ключ/значение и массивы для хранения индексированных списков. Однако, чтобы предоставить разработчикам больше гибкости, в спецификации ECMAScript 2015 появились два новых типа итерируемых объектов: карты (map), которые являются упорядоченными коллекциями пар ключ/значение, и сеты (set), которые содержат список уникальных значений.

В этой статье мы поговорим об объектах типов map и set, о том, что их отличает и объединяет с объектами и массивами, о свойствах и методах, доступных для этих типов, а также немного попрактикуемся.

Карты

Map — это коллекция пар ключ/значение, которая может использовать любой тип данных в качестве ключа и поддерживает порядок своих записей. Карты содержат как характеристики объектов (коллекция пар ключ/значение), так и массивов (упорядоченная коллекция), но имеют больше сходства с объектами. Это связано с тем, что хотя размер и порядок записей сохраняется, как и в массиве, сами по себе записи являются парами ключ/значение, как объекты.

Карты можно инициализировать с помощью синтаксиса new Map():

const map = new Map()

В результате будет создана пустая карта:

Output
Map(0) {}

Добавление значений в карту

Вы можете добавить значения в карту с помощью метода set(). Первый аргумент будет ключом, а второй — значением.

Представленный синтаксис добавляет в карту три пары ключ/значение:

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

Здесь мы начинаем понимать, что карты имеют черты объектов и массивов. Как и в случае массива, у нас есть индексированная коллекция, и мы можем увидеть количество элементов в карте по умолчанию. Карты используют синтаксис => для обозначения пар ключ/значение как key => value:

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

Этот пример выглядит аналогично обычному объекту со строковым ключом, но мы можем использовать любой тип данных в качестве ключа в картах.

Помимо ручной настройки значений в карте, мы можем также инициализировать карту с уже добавленными значениями. Мы используем массив массивов, содержащий два элемента, каждый из которых представляет одну пару ключ/значение и выглядит примерно так:

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

Используя следующий синтаксис, мы можем воссоздать ту же самую карту:

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

Примечание. Данный пример использует завершающие запятые, также именуемые как подвешенные запятые. Это практика форматирования JavaScript, когда конечный элемент в серии при объявлении коллекции данных имеет запятую в конце. Хотя это форматирование можно использовать для получения более чистых версий кода и облегчения работы с кодом, использовать его или нет решает разработчик. Дополнительную информацию о завершающих запятых см. в статье о завершающих запятых в веб-документации MDN.

Кстати, этот синтаксис выглядит так же, как и результат вызова Object.entries() для объекта. Это дает готовый способ для преобразования объекта в карту, как показано в следующем блоке кода:

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

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

Также вы можете превратить карту обратно в объект или массив с помощью одной строки кода.

Код ниже преобразует карту в объект:

const obj = Object.fromEntries(map)

В результате будет получено следующее значение obj:

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

Теперь мы преобразуем карту в массив:

const arr = Array.from(map)

В результате будет получен следующий массив arr:

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

Ключи карты

Карты принимают любой тип данных в качестве ключа и не разрешают использование дублирующих ключей. Мы можем продемонстрировать это, создав карту и используя нестроковые значения в качестве ключей, а также задав два значения для одного ключа.

Давайте проинициализируем карту нестроковыми ключами:

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 в качестве уникальных ключей:

Output
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.

В качестве примера создайте объект и используйте его в качестве ключа другого объекта:

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

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

В результате вы получите следующее:

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

Такой подход с картой не работает. Попробуйте создать объект и задать его как ключ для карты:

// 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?')

Ключом элемента карты теперь является созданный нами объект.

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

Необходимо отметить один важный момент, касающийся использования объекта или массива в качестве ключа: карта использует ссылку на объект для сравнения, а не литеральное значение объекта. В JavaScript {} === {} возвращает false, поскольку оба объекта не являются одинаковыми объектами, несмотря на то, что у них одно (пустое) значение.

Это означает, что при добавлении двух уникальных объектов с одинаковым значением будет создана карта с двумя записями:

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

В результате вы получите следующее:

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

Но при использовании одной ссылки на объект дважды будет создана карта с одной записью.

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

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

Результат будет выглядеть следующим образом:

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

Второй вызов set() обновляет тот же ключ, что и первый вызов, поэтому в итоге мы получаем карту с одним значением.

Получение и удаление элементов карты

Одним из недостатков работы с объектами является трудность их нумерации или работы со всеми ключами или значениями. Структура карты, напротив, имеет много встроенных свойств, которые делают работу с элементами более понятной.

Мы можем проинициализировать новую карту для демонстрации следующих методов и свойств: 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"

Одно конкретное преимущество карт по сравнению с объектами состоит в том, что вы можете получить размер карты в любой момент, как и в случае с массивом. Вы можете получить количество элементов карты с помощью свойства size. Это позволяет использовать меньше действий по сравнению с преобразованием объекта в массив и получением его длины.

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

Используйте метод delete(), чтобы удалить элемент карты по ключу. Метод будет возвращать булево значение — true, если элемент существовал и был удален, и false, если такой элемент не найден.

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

В результате будет получена следующая карта:

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

Наконец, вы можете удалить все значения карты с помощью map.clear().

// Empty a Map
map.clear()

Результат будет выглядеть следующим образом:

Output
Map(0) {}

Ключи, значения и записи для карт

Объекты могут получать ключи, значения и записи, используя свойства конструктора Object. Карты, напротив, имеют методы, которые позволяют нам получить ключи, значения и записи экземпляра карты.

Методы keys(), values() и entries() возвращают MapIterator, который похож на массив, где вы можете использовать цикл for...of для прохождения по всем значениям.

Здесь представлен другой пример карты, который мы можем использовать для демонстрации этих методов:

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

Метод keys() возвращает ключи:

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

Метод values() возвращает значения:

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

Метод entries() возвращает массив пар ключ/значение:

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

Итерация по карте

Карта имеет встроенный метод forEach, как и в случае с массивом, для встроенной итерации по карте. Однако есть определенное количество отличий в том, как они выполняют итерацию. Обратный вызов forEach карты проходит по value, key и map, в то время как версия для массива проходит по item, index и array.

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

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

Это огромное преимущество карты над объектом, так как объект нужно преобразовывать для использования keys(), values() или entries(), а простого способа получения свойств объекта без преобразования не существует.

Чтобы продемонстрировать это, давайте пробежимся по нашей карте и выведем пары ключ/значение в консоль:

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

Это даст нам следующее:

Output
1970: bell bottoms 1980: leg warmers 1990: flannel

Поскольку цикл for...of поддерживает итерируемые структуры, такие как карты и массивы, мы можем получить аналогичный результат, деструктурируя массив элементов карты:

// 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}`)
}

Свойства и методы карты

В нижеследующей таблице содержится список свойств и методов карты для быстрого просмотра:

Свойства/методы Описание Возвращает
set(key, value) Добавляет пару ключ/значение в карту Карта Объект
delete(key) Удаляет пару ключ/значение из карты по ключу Булево значение
get(key) Возвращает значение по ключу значение
has(key) Проверяет наличие элемента в карте по ключу Булево значение
clear() Удаляет все элементы из карты Нет данных
keys() Возвращает все ключи в карте Объект MapIterator
values() Возвращает все значения в карте Объект MapIterator
entries() Возвращает все ключи и значения в карте в виде [key, value] Объект MapIterator
forEach() Выполняет итерацию по карте в порядке добавления Нет данных
size Возвращает количество элементов в карте Число

Когда нужно использовать карту

В целом карты напоминают объекты внутри, которые хранят пары ключ/значение, но карты обладают рядом преимуществ над объектами:

  • Размер — карты имеют свойство size, в то время как объекты не имеют встроенного способа получения размера.
  • Итерация — карты являются итерируемыми, а объекты нет.
  • Гибкость — карты могут содержать любой тип данных (примитивы или объекты) в качестве ключа для значения, а объекты могут хранить только строки.
  • Упорядоченность — карты сохраняют порядок добавления, а объекты не имеют гарантированного порядка.

С учетом этих факторов карты являются мощной структурой данных, и вам стоит рассмотреть возможность их использования. Однако объекты также имеют ряд важных преимуществ:

  • JSON — объекты отлично работают с JSON.parse() и JSON.stringify(), двумя основными функциями для работы с JSON, стандартным форматом данных, с которым работает множество REST API.
  • Работа с одним элементом — работая с известным значением в объекте, вы можете получить к нему доступ напрямую с помощью ключа без необходимости использования метода, например метода get() карты.

Этот список поможет вам решить, подходит ли в вашем конкретном случае карта или объект.

Сет

Сет — это коллекция уникальных значений. В отличие от карты, сет концептуально больше походит на массив, а не объект, поскольку он представляет собой список значений, а не пар ключ/значение. Однако сет не является заменой массива, он служит дополнением для предоставления новых возможностей работы с дублирующимися данными.

Вы можете инициализировать сеты с помощью синтаксиса new Set().

const set = new Set()

В результате будет создан пустой сет:

Output
Set(0) {}

Элементы можно добавить в сет с помощью метода add() (не путайте с методом set(), доступным для карты, хотя они и похожи).

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

Поскольку сеты могут содержать только уникальные значения, любая попытка добавления уже существующего значения будет игнорироваться.

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

Примечание. Процесс сравнения, используемый для ключей карты, применяется и для элементов сета. Два объекта, имеющие одно и то же значение, но разные ссылки, не считаются равными.

Также вы можете инициализировать сеты с помощью массива значений. Если в массиве есть дублирующие значения, они будут удалены из сета.

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

Аналогично сет можно преобразовывать в массив с помощью одной строки кода:

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

Сеты имеют множество общих с картой методов и свойств, включая 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

Обратите внимание, что сет не позволяет получить доступ к значению по ключу или индексу, как, например, Map.get(key) или arr[index].

Ключи, значения и записи для сетов

Карта и сет имеют методы keys(), values() и entries(), которые возвращают итератор. Однако, хотя каждый из этих методов имеет конкретную функцию в карте, у сетов нет ключей, поэтому ключи являются псевдонимами для значений. Это означает, что keys() и values() будут возвращать один и тот же итератор, а entries() будет возвращать значение дважды. В случае с сетом только метод values()​​​ имеет смысл, а два других метода существуют для обеспечения согласованности и кросс-совместимости с картой.

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

Итерация сета

Как и карта, сет имеет встроенный метод forEach(). Поскольку у сетов нет ключей, первый и второй параметр обратного вызова forEach() возвращает одно и то же значение, так что нет необходимости его использования без совместимости с картой. Параметрами forEach() являются (value, key, set).

При работе с сетом можно использовать forEach() и for...of. Вначале рассмотрим итерацию с помощью 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);
}

В обоих случаях нам потребуется следующее:

Output
hi hello good day

Свойства и методы сета

В нижеследующей таблице содержится список свойств и методов сета для быстрого просмотра:

Свойства/методы Описание Возвращает
add(value) Добавляет новый элемент в сет Сет Объект
delete(value) Удаляет заданный элемент из сета Булево значение
has() Проверяет наличие элемента в сете Булево значение
clear() Удаляет все элементы из сета Нет данных
keys() Возвращает все значения сета (аналогично values()) Объект SetIterator
values() Возвращает все значения сета (аналогично keys()) Объект SetIterator
entries() Возвращает все значения сета в виде [value, value] Объект SetIterator
forEach() Выполняет итерацию по сету в порядке вставки Нет данных
size Возвращает количество элементов сета Число

Когда нужно использовать сет

Сет — это полезное дополнение для вашего набора инструментов JavaScript, особенно при работе с дублирующими значениями в данных.

В одной строке мы можем создать новый массив без дублирующих значений из массива, где есть повторяющиеся значения.

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

Это даст нам следующее:

Output
(3) [1, 2, 3]

Сет можно использовать для поиска идентичных, пересекающихся и отличных элементов двух наборов данных. Однако массивы имеют ряд преимуществ, включая дополнительные инструменты манипуляции данными благодаря методам sort(), map(), filter() и reduce(), а также прямую совместимость с методами JSON.

Заключение

Из этой статьи мы узнали, что карта представляет собой коллекцию упорядоченных пар ключ/значение, а сет — это коллекция уникальных значений. Обе эти структуры дают JavaScript дополнительные возможности и упрощают выполнение стандартных задач, например поиск длины коллекции пар ключ/значение и удаление дублирующих элементов из набора данных соответственно. С другой стороны, объекты и массивы традиционно используются для хранения и обработки данных в JavaScript, а также имеют прямую совместимость с JSON, что позволяет им оставаться самыми важными структурами данных, особенно при работе с REST API. Карты и сеты часто используются в качестве вспомогательных структур данных для объектов и массивов.

Если вы хотите узнать больше о JavaScript, перейдите на главную страницу нашей серии материалов Написание кода на JavaScript или перейдите к нашей серии Написание кода на Node.js для изучения материалов по серверной разработке.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors

Default avatar

Senior Technical Editor

Editor at DigitalOcean, fiction writer and podcaster elsewhere, always searching for the next good nautical pun!


Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel