JavaScript: Как использовать Коллекции — Map и Set
object
создаётся с помощью фигурных скобок {…}
и списка свойств. Свойство — пара ключ-значение, где ключ
должен быть строкой, а значение
может быть любого типа.
С другой стороны, array
— упорядоченная коллекция, содержащая данные любого типа. В JavaScript массивы создаются с помощью квадратных скобок […]
и допускают дублирование элементов.
До ES6 (ECMAScript 2015) объекты и массивы JavaScript были наиболее важными структурами данных для обработки коллекций данных. Помимо этого, у сообщества разработчиков не было большого выбора. Тем не менее комбинация объектов и массивов могла обрабатывать данные во многих сценариях.
Однако было несколько недостатков:
- Ключи объекта могут быть только типа
string
. - Объекты не сохраняют порядок вставленных элементов.
- У объектов отсутствуют некоторые полезные методы, что затрудняет их использование в некоторых ситуациях. Например, нелегко вычислить размер (
length
) объекта. Кроме того, перебор объекта не так прост. - Массивы — набор элементов, допускающий дублирование. Поддержка массивов, состоящих только из отдельных элементов, требует дополнительной логики и кода.
С введением ES6 мы получили две новые структуры данных, устраняющие вышеупомянутые недостатки: Map
и Set
. В этой статье мы внимательно рассмотрим обе и поймём как их использовать в разных ситуациях.
Map в JavaScript
Map — набор пар ключ-значение, где ключ может быть любого типа. Map
запоминает первоначальный порядок добавления элементов, это означает, что данные могут быть извлечены в том же порядке в каком они были вставлены.
Другими словами, Map
имеет характеристики как объекта, так и массива:
- Как и объект, поддерживает структуру пар ключ-значение.
- Как и массив, запоминает порядок вставки.
Как создать и инициализировать Map
в JavaScript
Новый Map
можно создать так:
const map = new Map();
Который вернёт пустой Map
:
Map(0) {}
Другой способ создания Map
— с начальными значениями. Вот как создать Map
с тремя парами ключ-значение:
const freeCodeCampBlog = new Map([
['name', 'freeCodeCamp'],
['type', 'blog'],
['writer', 'Tapas Adhikary'],
]);
Который возвращает Map
с тремя элементами:
Map(3) {"name" => "freeCodeCamp", "type" => "blog", "writer" => "Tapas Adhikary"}
Как добавить значения в Map
Чтобы добавить значение в Map
используйте метод set(ключ, значение)
.
Метод set(ключ, значение)
принимает два параметра, ключ
и значение
, где ключ
и значение
могут быть любого типа, примитивами (boolean
, string
, number
и т.д.) или объектами:
// Создаём map
const map = new Map();
// добавляем значения в map
map.set('name', 'freeCodeCamp');
map.set('type', 'blog');
map.set('writer', 'Tapas Adhikary');
// Вывод:
// Map(3) {"name" => "freeCodeCamp", "type" => "blog", "writer" => "Tapas Adhikary"}
Обратите внимание: если вы используете один и тот же ключ для добавления значения несколько раз, оно всегда будет заменять предыдущее значение:
// Добавляем другого автора
map.set('writer', 'Someone else!');
Вывод будет:
Map(3) {"name" => "freeCodeCamp", "type" => "blog", "writer" => "Someone else!"}
Как получить значение из Map
Для получения значения из Map
используйте метод get(ключ)
:
map.get('name'); // вернёт freeCodeCamp
Всё о ключах Map
Ключи Map
могут быть любого типа, примитивами или объектами. Это одно из основных различий между Map
и обычными объектами JavaScript, где ключ может быть только строкой:
// Создаём Map
const funMap = new Map();
funMap.set(360, 'My House Number'); // number в качестве ключа
funMap.set(true, 'I write blogs!'); // boolean в качестве ключа
let obj = {'name': 'tapas'}
funMap.set(obj, true); // object в качестве ключа
console.log(funMap);
Вот результат:
Map(3)
{
360 => "My House Number",
true => "I write blogs!",
{…} => true
}
Обычный объект JavaScript всегда обрабатывает ключ как строку. Даже когда вы ему передаёте примитив или объект, он внутренне преобразует ключ в строку:
// Создаём пустой объект
const funObj = {};
// добавляем свойство. Заметьте, что ключ передаётся как число.
funObj[360] = 'My House Number';
// Возвращает true, потому что число 360 было внутренне преобразовано в строку `360`!
console.log(funObj[360] === funObj['360']);
Свойства и методы Map
JavaScript Map
имеет встроенный свойства и методы, упрощающие его использование. Вот некоторые основные:
Используйте свойство
size
, чтобы узнать сколько элементов вMap
:console.log('Количество элементов в map: ', map.size);
Поиск элементов с помощью метода
has(ключ)
:// вернёт true, если map содержит элемент с ключём, 'John'
console.log(map.has('John'));
// вернёт false, если map не содержит элемент с ключём, 'Tapas'
console.log(map.has('Tapas'));Удаление элемента методом
delete(ключ)
:map.delete('Sam'); // удалит элемент с ключём, 'Sam'.
Для удаления всех элементов используйте метод
clear()
:// Очистить map удалив все элементы
map.clear();
map.size // Вернёт, 0
MapIterator
: keys()
, values()
и entries()
Методы keys()
, values()
и entries()
возвращают MapIterator
, что превосходно, потому что вы можете использовать цикл for-of
или forEach
непосредственно на нём.
Сначала создадим простой Map
:
const ageMap = new Map([
['Jack', 20],
['Alan', 34],
['Bill', 10],
['Sam', 9]
]);
Получим все ключи:
console.log(ageMap.keys());
// Вывод:
// MapIterator {"Jack", "Alan", "Bill", "Sam"}Получим все значения:
console.log(ageMap.values());
// Вывод:
// MapIterator {20, 34, 10, 9}Получим все записи (пары ключ-значение):
console.log(ageMap.entries());
// Вывод:
// MapIterator {"Jack" => 20, "Alan" => 34, "Bill" => 10, "Sam" => 9}
Как перебрать все элементы Map
Вы можете использовать цикл forEach
или for-of
для перебора Map
:
// c forEach
ageMap.forEach((value, key) => {
console.log(`${key} is ${value} years old!`);
});
// c for-of
for(const [key, value] of ageMap) {
console.log(`${key} is ${value} years old!`);
}
В обоих случаях результат будет одинаковым:
Jack is 20 years old!
Alan is 34 years old!
Bill is 10 years old!
Sam is 9 years old!
Как конвертировать Объект в Map
Вы можете столкнуться с ситуацией, когда нужно преобразовать object
в Map
структуру. Можно использовать метод entries
Object
и сделать так:
const address = {
'Tapas': 'Bangalore',
'James': 'Huston',
'Selva': 'Srilanka'
};
const addressMap = new Map(Object.entries(address));
Как конвертировать Map в Объект
Конвертировать Map
в объект можно с помощью метода fromEntries
:
Object.fromEntries(map)
Как конвертировать Map в Массив
Есть несколько способов преобразования Map в массив:
Использовать
Array.from(map)
:const map = new Map();
map.set('milk', 200);
map.set("tea", 300);
map.set('coffee', 500);
console.log(Array.from(map));Использовать оператор
Spread (...)
:console.log([...map]);
Map или Объект: Когда использовать?
У Map
есть характеристики, как object
, так и array
. Однако Map
больше похож на object
, чем на array
из-за природы хранения данных в формате ключ-значение
.
На этом сходство с объектами заканчивается. Как вы видели, Map
во многом отличается. Итак, какой из этих типов и когда следует использовать? Как вы считаете?
Когда использовать Map
:
- Ваши потребности не так просты. Вы можете захотеть создать ключи, которые не являются строками. Хранение объекта в качестве ключа — очень мощный подход.
Map
даёт такую возможность по умолчанию. - Вам нужна структура данных с упорядоченными элементами. Обычные объекты не сохраняют порядок своих записей.
- Вы ищете гибкость не полагаясь на внешнюю библиотеку, такую, как lodash. В конечном итоге вы можете использовать библиотеку подобную lodash, так как у обычных объектов нет методов
has()
,values()
,delete()
или свойствsize
.Map
упрощает задачу предоставляя такие методы по умолчанию.
Когда использовать object
:
- У вас нет ни одной из вышеперечисленных потребностей.
- Вы полагаетесь на
JSON.parse()
, посколькуMap
не может быть проанализирована им.
Set
в JavaScript
Set
— коллекция уникальных элементов, которые могут быть любого типа. Так же Set
является упорядоченной коллекцией — элементы будут извлекаться в том же порядке, в каком они были вставлены.
Set
в JavaScript ведёт себя так же, как математические множества.
Как создать и инициализировать Set
Новый Set можно создать следующим образом:
const set = new Set();
console.log(set);
// Вывод:
Set(0) {}
Вот так можно создать Set с начальными значениями:
const fruitSet = new Set(['🍉', '🍎', '🍈', '🍏']);
console.log(fruitSet);
// Вывод:
Set(4) {"🍉", "🍎", "🍈", "🍏"}
Методы и Свойства Set
В Set
есть методы для добавления элемента, удаления одного из элементов, проверка наличия элемента и полная очистка (удаление всех элементов):
Свойство
size
возвращает количество элементовSet
:set.size
Для добавления элементов в
Set
используем методadd(элемент)
:// Создаём Set - saladSet
const saladSet = new Set();
// Add some vegetables to it
saladSet.add('🍅'); // помидор
saladSet.add('🥑'); // авокадо
saladSet.add('🥕'); // морковь
saladSet.add('🥒'); // огурец
console.log(saladSet);
// Вывод:
// Set(4) {"🍅", "🥑", "🥕", "🥒"}Мне нравятся огурцы! Добавим ещё один?
О нет, я не могу —
Set
это набор уникальных элементов:saladSet.add('🥒');
console.log(saladSet);
// Вывод:
// Set(4) {"🍅", "🥑", "🥕", "🥒"}Вывод такой же, как раньше — в
saladSet
ничего не добавилось.Используем метод
has(элемент)
для поиска моркови(🥕) или брокколи(🥦) вSet
:// В saladSet есть 🥕, результат: true
console.log('В салате есть морковь?', saladSet.has('🥕'));
// В saladSet нет 🥦, результат false
console.log('В салате есть брокколи?', saladSet.has('🥦'));Используем метод
delete(элемент)
для удаления авокадо(🥑) из салата:saladSet.delete('🥑');
console.log('Мне не нравится 🥑, удалим его из салата:', saladSet);Теперь
saladSet
выглядит следующим образом:Set(3) {"🍅", "🥕", "🥒"}
Используем метод
clear()
для удаления всех элементовsaladSet
:saladSet.clear();
Как перебрать все элементы Set
В Set
есть метод values()
, возвращающий SetIterator
для получения всех значений:
// Создаём Set
const houseNos = new Set([360, 567, 101]);
// Получаем SetIterator используя метод `values()`
console.log(houseNos.values());
// Вывод:
SetIterator {360, 567, 101}
Можно использовать цикл forEach
или for-of
для получения значений.
Интересно, что JavaScript пытается сделать Set
совместимым с Map
. Вот почему у него есть два похожих метода keys()
и entry()
, как в Map
.
Поскольку в Set
нет ключей, метод keys()
возвращает SetIterator
для получения значений:
console.log(houseNos.keys());
// Вывод:
// SetIterator {360, 567, 101}
Метод entry()
возвращает итератор для извлечения пар ключ-значение
. Опять же в Set
нет ключей, поэтому entry()
возвращает SetIterator
для получения пар значение-значение
:
console.log(houseNos.entries());
// Вывод:
// SetIterator {360 => 360, 567 => 567, 101 => 101}
Мы можем вывести все значения Set
используя циклы forEach
и for-of
:
// forEach
houseNos.forEach((value) => {
console.log(value);
});
// for-of
for(const value of houseNos) {
console.log(value);
}
Вывод в обоих случаях одинаковый:
360
567
101
Set и Массивы
Массив, как и Set
, позволяет добавлять и удалять элементы. Но Set
совершенно иной тип и не предназначен для замены массивов.
Основное отличие в том, что массивы могут иметь повторяющиеся элементы. Кроме того, некоторые операции Set
, такие, как delete()
выполняются быстрее, чем операции с массивами, такие как shift()
или splice()
.
Думайте о Set
, как о расширении обычного массива, только с большим количеством мышц. Структура данных Set
не является заменой массива, оба могут решать интересные задачи.
Как конвертировать Set
в Массив
Set
очень просто конвертируется в массив:
const arr = [...houseNos];
console.log(arr);
// Вывод:
// (3) [360, 567, 101]
Получение уникальных значений из массива с помощью Set
Создание Set
— простой способ удаления повторяющихся элементов из массива:
// Создадим массив mixedFruit с несколькими дубликатами фруктов
const mixedFruit = ['🍉', '🍎', '🍉', '🍈', '🍏', '🍎', '🍈'];
// Создаём из массива уникальный набор
const mixedFruitSet = new Set(mixedFruit);
console.log(mixedFruitSet);
// Вывод
// Set(4) {'🍉', '🍎', '🍈', '🍏'}
Set и Объект
Set
может содержать элементы любого типа, даже объект:
// Создаём объект person
const person = {
'name': 'Alex',
'age': 32
};
// Создаём Set и добавляем в него объект
const pSet = new Set();
pSet.add(person);
console.log(pSet);
// Вывод:
// Set(1)
// [[Entries]]
// 0:
// value: {name: 'Alex', age: 32}
// size: 1
// [[Prototype]]: Set
Нет ничего удивительного — Set
содержит один элемент, являющийся объектом.
Изменим свойство объекта и снова добавим его в Set
:
// Изменим имя
person.name = 'Bob';
// Добавим объект person в Set снова
pSet.add(person);
console.log(pSet);
// Вывод:
//Set(1)
// [[Entries]]
// 0:
// value: {name: 'Bob', age: 32}
// size: 1
// [[Prototype]]: Set
Set
— набор уникальных элементов. Изменив свойство объекта, мы не изменили сам объект. Следовательно, Set
не допустит дублирования элементов.
Set
— отличная структура данных, которую можно использовать в дополнение к массивам. Однако у него нет большого преимущества пред обычными массивами.
Используйте Set
, когда нужно поддерживать отдельный набор данных для выполнения операций над наборами, как объединение
, пересечение
и т.д.