Автор выбрал фонд Open Internet/Free Speech для получения пожертвования в рамках программы Write for DOnations.
В JavaScript разработчики часто тратят много времени на решение того, какую структуру данных следует использовать. Это вызвано тем, что выбор правильной структуры данных упрощает последующее управление этими данными, экономя время и упрощая понимание кода. Двумя преобладающими структурами для хранения коллекций данных являются объекты и массивы (тип объекта). Разработчики используют объекты для хранения пары ключ/значение и массивы для хранения индексированных списков. Однако, чтобы предоставить разработчикам больше гибкости, в спецификации ECMAScript 2015 появились два новых типа итерируемых объектов: карты (map), которые являются упорядоченными коллекциями пар ключ/значение, и сеты (set), которые содержат список уникальных значений.
В этой статье мы поговорим об объектах типов map и set, о том, что их отличает и объединяет с объектами и массивами, о свойствах и методах, доступных для этих типов, а также немного попрактикуемся.
Map — это коллекция пар ключ/значение, которая может использовать любой тип данных в качестве ключа и поддерживает порядок своих записей. Карты содержат как характеристики объектов (коллекция пар ключ/значение), так и массивов (упорядоченная коллекция), но имеют больше сходства с объектами. Это связано с тем, что хотя размер и порядок записей сохраняется, как и в массиве, сами по себе записи являются парами ключ/значение, как объекты.
Карты можно инициализировать с помощью синтаксиса new Map()
:
В результате будет создана пустая карта:
OutputMap(0) {}
Вы можете добавить значения в карту с помощью метода set()
. Первый аргумент будет ключом, а второй — значением.
Представленный синтаксис добавляет в карту
три пары ключ/значение:
Здесь мы начинаем понимать, что карты имеют черты объектов и массивов. Как и в случае массива, у нас есть индексированная коллекция, и мы можем увидеть количество элементов в карте по умолчанию. Карты используют синтаксис =>
для обозначения пар ключ/значение как key => value
:
OutputMap(3)
0: {"firstName" => "Luke"}
1: {"lastName" => "Skywalker"}
2: {"occupation" => "Jedi Knight"}
Этот пример выглядит аналогично обычному объекту со строковым ключом, но мы можем использовать любой тип данных в качестве ключа в картах.
Помимо ручной настройки значений в карте, мы можем также инициализировать карту с уже добавленными значениями. Мы используем массив массивов, содержащий два элемента, каждый из которых представляет одну пару ключ/значение и выглядит примерно так:
Используя следующий синтаксис, мы можем воссоздать ту же самую карту:
Примечание. Данный пример использует завершающие запятые, также именуемые как подвешенные запятые. Это практика форматирования JavaScript, когда конечный элемент в серии при объявлении коллекции данных имеет запятую в конце. Хотя это форматирование можно использовать для получения более чистых версий кода и облегчения работы с кодом, использовать его или нет решает разработчик. Дополнительную информацию о завершающих запятых см. в статье о завершающих запятых в веб-документации MDN.
Кстати, этот синтаксис выглядит так же, как и результат вызова Object.entries()
для объекта. Это дает готовый способ для преобразования объекта в карту, как показано в следующем блоке кода:
Также вы можете превратить карту обратно в объект или массив с помощью одной строки кода.
Код ниже преобразует карту в объект:
В результате будет получено следующее значение obj
:
Output{firstName: "Luke", lastName: "Skywalker", occupation: "Jedi Knight"}
Теперь мы преобразуем карту в массив:
В результате будет получен следующий массив arr
:
Output[ ['firstName', 'Luke'],
['lastName', 'Skywalker'],
['occupation', 'Jedi Knight'] ]
Карты принимают любой тип данных в качестве ключа и не разрешают использование дублирующих ключей. Мы можем продемонстрировать это, создав карту и используя нестроковые значения в качестве ключей, а также задав два значения для одного ключа.
Давайте проинициализируем карту нестроковыми ключами:
В этом примере будет переопределен первый ключ 1
на последующий ключ и будет рассмотрено использование строки '1'
и числа 1
в качестве уникальных ключей:
Output0: {"1" => "String one"}
1: {1 => "Number one"}
2: {true => "A Boolean"}
Хотя широко распространено мнение, что стандартный объект JavaScript может работать с числами, булевыми значениями и другими примитивами как с ключами, на самом деле это не так, поскольку объекты преобразуют все ключи в строки.
В качестве примера выполните инициализацию объекта с числовым ключом и сравните числовой ключ 1
со строковым ключом "1"
:
Поэтому, если вы попробуете использовать объект в качестве ключа, он будет отображать строку object Object
.
В качестве примера создайте объект и используйте его в качестве ключа другого объекта:
В результате вы получите следующее:
Output{[object Object]: "What will happen?"}
Такой подход с картой не работает. Попробуйте создать объект и задать его как ключ для карты:
Ключом
элемента карты теперь является созданный нами объект.
Outputkey: {foo: "bar"}
value: "What will happen?"
Необходимо отметить один важный момент, касающийся использования объекта или массива в качестве ключа: карта использует ссылку на объект для сравнения, а не литеральное значение объекта. В JavaScript {} === {}
возвращает false
, поскольку оба объекта не являются одинаковыми объектами, несмотря на то, что у них одно (пустое) значение.
Это означает, что при добавлении двух уникальных объектов с одинаковым значением будет создана карта с двумя записями:
В результате вы получите следующее:
OutputMap(2) {{…} => "One", {…} => "Two"}
Но при использовании одной ссылки на объект дважды будет создана карта с одной записью.
Результат будет выглядеть следующим образом:
OutputMap(1) {{…} => "Two"}
Второй вызов set()
обновляет тот же ключ, что и первый вызов, поэтому в итоге мы получаем карту с одним значением.
Одним из недостатков работы с объектами является трудность их нумерации или работы со всеми ключами или значениями. Структура карты, напротив, имеет много встроенных свойств, которые делают работу с элементами более понятной.
Мы можем проинициализировать новую карту для демонстрации следующих методов и свойств: delete()
, has()
, get()
и size
.
Используйте метод has()
для проверки наличия элемента в карте. has()
будет возвращать булево значение.
Используйте метод get()
для получения значения по ключу.
Одно конкретное преимущество карт по сравнению с объектами состоит в том, что вы можете получить размер карты в любой момент, как и в случае с массивом. Вы можете получить количество элементов карты с помощью свойства size
. Это позволяет использовать меньше действий по сравнению с преобразованием объекта в массив и получением его длины.
Используйте метод delete()
, чтобы удалить элемент карты по ключу. Метод будет возвращать булево значение — true
, если элемент существовал и был удален, и false
, если такой элемент не найден.
В результате будет получена следующая карта:
OutputMap(3) {"animal" => "otter", "shape" => "triangle", "country" => "Bulgaria"}
Наконец, вы можете удалить все значения карты с помощью map.clear()
.
Результат будет выглядеть следующим образом:
OutputMap(0) {}
Объекты могут получать ключи, значения и записи, используя свойства конструктора Object
. Карты, напротив, имеют методы, которые позволяют нам получить ключи, значения и записи экземпляра карты.
Методы keys()
, values()
и entries()
возвращают MapIterator
, который похож на массив, где вы можете использовать цикл for...of
для прохождения по всем значениям.
Здесь представлен другой пример карты, который мы можем использовать для демонстрации этих методов:
Метод keys()
возвращает ключи:
OutputMapIterator {1970, 1980, 1990}
Метод values()
возвращает значения:
OutputMapIterator {"bell bottoms", "leg warmers", "flannel"}
Метод entries()
возвращает массив пар ключ/значение:
OutputMapIterator {1970 => "bell bottoms", 1980 => "leg warmers", 1990 => "flannel"}
Карта имеет встроенный метод forEach
, как и в случае с массивом, для встроенной итерации по карте. Однако есть определенное количество отличий в том, как они выполняют итерацию. Обратный вызов forEach
карты проходит по value
, key
и map
, в то время как версия для массива проходит по item
, index
и array
.
Это огромное преимущество карты над объектом, так как объект нужно преобразовывать для использования keys()
, values()
или entries()
, а простого способа получения свойств объекта без преобразования не существует.
Чтобы продемонстрировать это, давайте пробежимся по нашей карте и выведем пары ключ/значение в консоль:
Это даст нам следующее:
Output1970: bell bottoms
1980: leg warmers
1990: flannel
Поскольку цикл for...of
поддерживает итерируемые структуры, такие как карты и массивы, мы можем получить аналогичный результат, деструктурируя массив элементов карты:
В нижеследующей таблице содержится список свойств и методов карты для быстрого просмотра:
Свойства/методы | Описание | Возвращает |
---|---|---|
set(key, value) |
Добавляет пару ключ/значение в карту | Карта Объект |
delete(key) |
Удаляет пару ключ/значение из карты по ключу | Булево значение |
get(key) |
Возвращает значение по ключу | значение |
has(key) |
Проверяет наличие элемента в карте по ключу | Булево значение |
clear() |
Удаляет все элементы из карты | Нет данных |
keys() |
Возвращает все ключи в карте | Объект MapIterator |
values() |
Возвращает все значения в карте | Объект MapIterator |
entries() |
Возвращает все ключи и значения в карте в виде [key, value] |
Объект MapIterator |
forEach() |
Выполняет итерацию по карте в порядке добавления | Нет данных |
size |
Возвращает количество элементов в карте | Число |
В целом карты напоминают объекты внутри, которые хранят пары ключ/значение, но карты обладают рядом преимуществ над объектами:
size
, в то время как объекты не имеют встроенного способа получения размера.С учетом этих факторов карты являются мощной структурой данных, и вам стоит рассмотреть возможность их использования. Однако объекты также имеют ряд важных преимуществ:
JSON.parse()
и JSON.stringify()
, двумя основными функциями для работы с JSON, стандартным форматом данных, с которым работает множество REST API.get()
карты.Этот список поможет вам решить, подходит ли в вашем конкретном случае карта или объект.
Сет — это коллекция уникальных значений. В отличие от карты, сет концептуально больше походит на массив, а не объект, поскольку он представляет собой список значений, а не пар ключ/значение. Однако сет не является заменой массива, он служит дополнением для предоставления новых возможностей работы с дублирующимися данными.
Вы можете инициализировать сеты с помощью синтаксиса new Set()
.
В результате будет создан пустой сет:
OutputSet(0) {}
Элементы можно добавить в сет с помощью метода add()
(не путайте с методом set()
, доступным для карты, хотя они и похожи).
Поскольку сеты могут содержать только уникальные значения, любая попытка добавления уже существующего значения будет игнорироваться.
Примечание. Процесс сравнения, используемый для ключей карты, применяется и для элементов сета. Два объекта, имеющие одно и то же значение, но разные ссылки, не считаются равными.
Также вы можете инициализировать сеты с помощью массива значений. Если в массиве есть дублирующие значения, они будут удалены из сета.
OutputSet(3) {"Beethoven", "Mozart", "Chopin"}
Аналогично сет можно преобразовывать в массив с помощью одной строки кода:
Output(3) ["Beethoven", "Mozart", "Chopin"]
Сеты имеют множество общих с картой методов и свойств, включая delete()
, has()
, clear()
и size
.
Обратите внимание, что сет не позволяет получить доступ к значению по ключу или индексу, как, например, Map.get(key)
или arr[index]
.
Карта и сет имеют методы keys()
, values()
и entries()
, которые возвращают итератор. Однако, хотя каждый из этих методов имеет конкретную функцию в карте, у сетов нет ключей, поэтому ключи являются псевдонимами для значений. Это означает, что keys()
и values()
будут возвращать один и тот же итератор, а entries()
будет возвращать значение дважды. В случае с сетом только метод values()
имеет смысл, а два других метода существуют для обеспечения согласованности и кросс-совместимости с картой.
OutputSetIterator {1, 2, 3}
Как и карта, сет имеет встроенный метод forEach()
. Поскольку у сетов нет ключей, первый и второй параметр обратного вызова forEach()
возвращает одно и то же значение, так что нет необходимости его использования без совместимости с картой. Параметрами forEach()
являются (value, key, set)
.
При работе с сетом можно использовать forEach()
и for...of
. Вначале рассмотрим итерацию с помощью forEach()
:
Затем мы сможем написать версию for...of
:
В обоих случаях нам потребуется следующее:
Outputhi
hello
good day
В нижеследующей таблице содержится список свойств и методов сета для быстрого просмотра:
Свойства/методы | Описание | Возвращает |
---|---|---|
add(value) |
Добавляет новый элемент в сет | Сет Объект |
delete(value) |
Удаляет заданный элемент из сета | Булево значение |
has() |
Проверяет наличие элемента в сете | Булево значение |
clear() |
Удаляет все элементы из сета | Нет данных |
keys() |
Возвращает все значения сета (аналогично values() ) |
Объект SetIterator |
values() |
Возвращает все значения сета (аналогично keys() ) |
Объект SetIterator |
entries() |
Возвращает все значения сета в виде [value, value] |
Объект SetIterator |
forEach() |
Выполняет итерацию по сету в порядке вставки | Нет данных |
size |
Возвращает количество элементов сета | Число |
Сет — это полезное дополнение для вашего набора инструментов JavaScript, особенно при работе с дублирующими значениями в данных.
В одной строке мы можем создать новый массив без дублирующих значений из массива, где есть повторяющиеся значения.
Это даст нам следующее:
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.
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!