JavaScript: Как использовать Коллекции — Map и Set

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

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, когда нужно поддерживать отдельный набор данных для выполнения операций над наборами, как объединение, пересечение и т.д.

Дополнительные материалы

Предыдущая Статья

JavaScript: Более безопасное чтение и запись URL

Следующая Статья

PHP: Абстрактная Фабрика