Автор выбрал фонд Open Internet/Free Speech для получения пожертвования в рамках программы Write for DOnations.
В JavaScript разработчики часто тратят много времени на решение того, какую структуру данных следует использовать. Это вызвано тем, что выбор правильной структуры данных упрощает последующее управление этими данными, экономя время и упрощая понимание кода. Двумя преобладающими структурами для хранения коллекций данных являются объекты и массивы (тип объекта). Разработчики используют объекты для хранения пары ключ/значение и массивы для хранения индексированных списков. Однако, чтобы предоставить разработчикам больше гибкости, в спецификации ECMAScript 2015 появились два новых типа итерируемых объектов: карты (map), которые являются упорядоченными коллекциями пар ключ/значение, и сеты (set), которые содержат список уникальных значений.
В этой статье мы поговорим об объектах типов map и set, о том, что их отличает и объединяет с объектами и массивами, о свойствах и методах, доступных для этих типов, а также немного попрактикуемся.
Map — это коллекция пар ключ/значение, которая может использовать любой тип данных в качестве ключа и поддерживает порядок своих записей. Карты содержат как характеристики объектов (коллекция пар ключ/значение), так и массивов (упорядоченная коллекция), но имеют больше сходства с объектами. Это связано с тем, что хотя размер и порядок записей сохраняется, как и в массиве, сами по себе записи являются парами ключ/значение, как объекты.
Карты можно инициализировать с помощью синтаксиса new Map()
:
const map = new Map()
В результате будет создана пустая карта:
OutputMap(0) {}
Вы можете добавить значения в карту с помощью метода set()
. Первый аргумент будет ключом, а второй — значением.
Представленный синтаксис добавляет в карту
три пары ключ/значение:
map.set('firstName', 'Luke')
map.set('lastName', 'Skywalker')
map.set('occupation', 'Jedi Knight')
Здесь мы начинаем понимать, что карты имеют черты объектов и массивов. Как и в случае массива, у нас есть индексированная коллекция, и мы можем увидеть количество элементов в карте по умолчанию. Карты используют синтаксис =>
для обозначения пар ключ/значение как key => value
:
OutputMap(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
в качестве уникальных ключей:
Output0: {"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?')
Ключом
элемента карты теперь является созданный нами объект.
Outputkey: {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')
В результате вы получите следующее:
OutputMap(2) {{…} => "One", {…} => "Two"}
Но при использовании одной ссылки на объект дважды будет создана карта с одной записью.
// Add the same exact object twice as keys to a Map
const obj = {}
map.set(obj, 'One')
map.set(obj, 'Two')
Результат будет выглядеть следующим образом:
OutputMap(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
В результате будет получена следующая карта:
OutputMap(3) {"animal" => "otter", "shape" => "triangle", "country" => "Bulgaria"}
Наконец, вы можете удалить все значения карты с помощью map.clear()
.
// Empty a Map
map.clear()
Результат будет выглядеть следующим образом:
OutputMap(0) {}
Объекты могут получать ключи, значения и записи, используя свойства конструктора Object
. Карты, напротив, имеют методы, которые позволяют нам получить ключи, значения и записи экземпляра карты.
Методы keys()
, values()
и entries()
возвращают MapIterator
, который похож на массив, где вы можете использовать цикл for...of
для прохождения по всем значениям.
Здесь представлен другой пример карты, который мы можем использовать для демонстрации этих методов:
const map = new Map([
[1970, 'bell bottoms'],
[1980, 'leg warmers'],
[1990, 'flannel'],
])
Метод keys()
возвращает ключи:
map.keys()
OutputMapIterator {1970, 1980, 1990}
Метод values()
возвращает значения:
map.values()
OutputMapIterator {"bell bottoms", "leg warmers", "flannel"}
Метод entries()
возвращает массив пар ключ/значение:
map.entries()
OutputMapIterator {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}`)
})
Это даст нам следующее:
Output1970: 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.parse()
и JSON.stringify()
, двумя основными функциями для работы с JSON, стандартным форматом данных, с которым работает множество REST API.get()
карты.Этот список поможет вам решить, подходит ли в вашем конкретном случае карта или объект.
Сет — это коллекция уникальных значений. В отличие от карты, сет концептуально больше походит на массив, а не объект, поскольку он представляет собой список значений, а не пар ключ/значение. Однако сет не является заменой массива, он служит дополнением для предоставления новых возможностей работы с дублирующимися данными.
Вы можете инициализировать сеты с помощью синтаксиса new Set()
.
const set = new Set()
В результате будет создан пустой сет:
OutputSet(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'])
OutputSet(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()
OutputSetIterator {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);
}
В обоих случаях нам потребуется следующее:
Outputhi
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.
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!