Коллекции Map и Set

Итак, мы с вами рассмотрели примитивные типы, объекты и массивы. Для полноты картины не хватает еще двух структур данных – это карты (Map) и наборы (Set). И на этом занятии мы с ними познакомимся. Начнем с карт Map.

Map

Помните, когда мы говорили об объектах, то отмечали, что ключи (имена свойств) являются строками – всегда строки. Так вот, в отличие от объектов в Map в качестве ключа могут выступать не только строки, но вообще любые типы данных.

У Map есть такие методы и свойства:

  • new Map() – создаёт коллекцию;
  • map.set(key, value) – записывает по ключу key значение value;
  • map.get(key) – возвращает значение по ключу или undefined, если ключ key отсутствует;
  • map.has(key) – возвращает true, если ключ key присутствует в коллекции, иначе false;
  • map.delete(key) – удаляет элемент по ключу key;
  • map.clear() – очищает коллекцию от всех элементов;
  • map.size – возвращает текущее количество элементов.

Рассмотрим их. Например:

let m = new Map();
 
m.set("string", "строка");
m.set(7, "простое число");
m.set(true, {descr: "boolean", value: true});
 
console.log( m.get("string") );
console.log( m.get(7) );
console.log( m.get(true) );

В качестве ключей можно использовать и объекты, например:

let user = {
    name: "JavaScript",
    type: "ES6"
};
 
let m = new Map();
m.set(user, "объект user");
console.log( m.get(user) );

Часто коллекцию Map используют благодаря этому свойству – возможности использования объектов в качестве ключей. Ни одна другая структура в JavaScript не обладает таким свойством.

Чтобы сравнивать ключи, объект Map использует алгоритм SameValueZero. Это почти такое же сравнение, что и ===, с той лишь разницей, что NaN считается равным NaN. Так что NaN также может использоваться в качестве ключа. Причем этот встроенный алгоритм сравнения не может быть заменён или модифицирован.

Создать объект Map также можно на основе двумерного массива:

let car = new Map([
         ["model", "opel"],
         ["color", 0xff],
         ["price", 1000]
    ]);

Здесь каждый элемент массива интерпретируется как «ключ – значение». В итоге коллекция car содержит три элемента с ключами model, color, price и соответствующими значениями.

Вообще, объект Map можно создать на основе любого итерируемого объекта, представляющего данные в формате «ключ - значение». Например, можно взять некий объект:

let book = {
    author: "Пушкин",
    title: "Онегин",
    pages: 100,
    price: 80
};

И создать из него карту:

let lib = new Map(Object.entries(book));

Здесь метод объекта entries преобразует объект book в массив с элементами «ключ – значение», на основе которого и формируется lib.

Есть также противоположный метод Object.fromEntries, который из двумерного массива по формату «ключ-значение», формирует объект:

let prices = Object.fromEntries([
    ['banana', 1],
    ['orange', 2],
    ['meat', 4]
]);

В частности, этот принцип используется для преобразования Map в Object:

let objLib = Object.fromEntries(lib.entries());
console.log(objLib);

Здесь метод entries коллекции Map, также как и у Object, преобразует содержимое в двумерный массив по формату «ключ-значение». Затем, с помощью метода fromEntries этот двумерный массив преобразуется в объект. Вот так можно Map превратить в Object.

Но, вернемся к коллекции car, переберем его содержимое. Для этого, обычно, используется цикл for of следующим образом:

for (let value of car) {
    console.log(value);
}

В результате value принимает вид массива [ключ, значение] и выводится в консоль.

Если нам нужно выбрать только ключи из коллекции car, то можно сделать так:

for (let key of car.keys()) {
    console.log(key);
}

Здесь мы в цикл передаем итерируемый (перебираемый) объект по ключам и в цикле выводим ключи в консоль.

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

for (let value of car.values()) {
    console.log(value);
}

В отличие от обычных объектов Object, в Map перебор происходит в том же порядке, в каком происходило добавление элементов.

Еще одним вариантом перебора служит цикл forEach, встроенный в объект Map:

Map.forEach( function(value, key, map) {
// что-то делаем
});

Например, можно вывести в консоль информацию объекта Map следующим образом:

car.forEach((value, key) => {
    console.log( `car[${key}] = ${value}` );
});

Здесь используется стрелочная функция с двумя входными аргументами value и key.

Set

Коллекция Set формируется из уникальных данных (без ключей – только данные). Уникальность означает, что одно и то же значение не может быть добавлено дважды – оно просто проигнорируется.

Данный объект имеет следующие методы и свойства:

  • new Set(iterable) – создаёт Set, если в качестве аргумента был предоставлен итерируемый объект (обычно это массив), то копирует его значения в Set;
  • set.add(value) – добавляет значение (если оно уже есть, то ничего не происходит), возвращает тот же объект set;
  • set.delete(value) – удаляет значение, возвращает true если value было найдено и удалено, иначе false;
  • set.has(value) – возвращает true, если значение присутствует в коллекции, иначе false;
  • set.clear() – удаляет все значения из набора;
  • set.size – возвращает количество элементов в наборе.

Например, мы ожидаем посетителей, и нам необходимо составить их список. Но повторные визиты не должны приводить к дубликатам. Каждый посетитель должен появиться в списке только один раз. Здесь как раз и подойдет множество Set:

let guests = new Set();
 
let alex = { name: "Alexey", old: 25 };
let oleg = { name: "Oleg", old: 32 };
let masha = { name: "Masha", old: 18 };
 
guests.add(alex);
guests.add(oleg);
guests.add(masha);
guests.add(alex);
guests.add(masha);

Выведем в консоль получившийся набор:

for (let guest of guests) {
    console.log(guest.name);
}

Получим только троих гостей. Все так, как и должно быть.

Перебрать объект Set также можно и с помощью встроенного метода forEach. Он имеет следующий синтаксис:

Set.forEach( function(value, valueAgain, set) {
// что-то делаем
});

Здесь второй аргумент valueAgain добавлен для синтаксической совместимости данного метода с аналогичным методом из Map. Это бывает полезно, если программист решит вместо Set использовать Map. Тогда ему достаточно будет поменять Set на Map и все заработает уже с новым объектом.

Выполним перебор элементов с помощью forEach:

guests.forEach( (item) => {
    console.log(item.name+": "+item.old);
});

Как видите, все достаточно просто. Перебор Map и Set всегда осуществляется в порядке добавления элементов, так что нельзя сказать, что это – неупорядоченные коллекции, но поменять порядок элементов или получить элемент напрямую по его номеру здесь не получится.

Видео по теме