Map и Set
Зачем это нужно
Объекты и массивы закрывают большинство задач, но не все. Иногда нужен словарь, где ключами
могут быть не только строки, или важен быстрый подсчёт количества. А иногда нужен набор без повторов:
уникальные посетители, список тегов, проверка «видели ли мы это раньше». Для этого в JavaScript есть
две коллекции — Map и Set.
Set — набор уникальных значений
const tags = new Set();
tags.add("js");
tags.add("css");
tags.add("js"); // повтор — игнорируется
console.log(tags.size);
console.log(tags.has("css"));
tags.delete("css");
console.log([...tags]);
Вывод:
2
true
["js"]
Set можно создать сразу из массива — повторы исчезнут:
const visits = ["Аня", "Тимур", "Аня", "Ира", "Тимур"];
const people = new Set(visits);
console.log(people.size);
console.log([...people]);
Вывод:
3
["Аня","Тимур","Ира"]
[...set] превращает набор обратно в массив. Порядок — как значения добавлялись.
Перебор Set
for (const tag of new Set(["a", "b"])) {
console.log(tag);
}
Вывод:
a
b
Map — словарь
Map хранит пары «ключ → значение», как объект, но:
- ключом может быть что угодно: число, объект, даже функция (у объекта ключи — только строки);
- размер — свойство
size; - нет «встроенных» ключей вроде
constructor, которые есть у любого объекта; - порядок ключей всегда равен порядку добавления.
const stock = new Map();
stock.set("apple", 10);
stock.set("pear", 4);
stock.set("apple", 12); // перезапись
console.log(stock.get("apple"));
console.log(stock.get("plum")); // нет такого ключа
console.log(stock.has("pear"), stock.size);
stock.delete("pear");
console.log([...stock]);
Вывод:
12
undefined
true 2
[["apple",12]]
Map можно создать сразу из массива пар: new Map([["a", 1], ["b", 2]]).
Перебор Map
const prices = new Map([["tea", 50], ["coffee", 90]]);
for (const [name, price] of prices) {
console.log(`${name}: ${price}`);
}
console.log([...prices.keys()], [...prices.values()]);
Вывод:
tea: 50
coffee: 90
["tea","coffee"] [50,90]
Каждый элемент Map при переборе — пара [ключ, значение], её удобно разбирать деструктуризацией.
Объект ↔ Map
const obj = { a: 1, b: 2 };
const map = new Map(Object.entries(obj));
const back = Object.fromEntries(map);
console.log(map.get("b"), back);
Вывод:
2 {"a":1,"b":2}
Что выбрать?
| Задача | Чем решать |
|---|---|
| Описание одной вещи (пользователь, товар) | объект {} |
| Список по порядку | массив [] |
| Подсчёт, словарь с произвольными ключами, частые добавления/удаления | Map |
| Уникальность, быстрая проверка «есть ли» | Set |
Как это работает внутри
Set и Map устроены как хеш-таблица: по значению ключа сразу вычисляется место, где его искать.
Поэтому set.has(x) работает мгновенно даже для миллиона элементов. А array.includes(x) перебирает
массив с начала — для большого массива и многих проверок это в тысячи раз медленнее. Если в цикле
проверяешь «есть ли элемент в другом массиве» — сначала сделай из того массива Set.
Уникальность в Set проверяется как === (кроме NaN, который считается равным себе). Поэтому два
разных объекта { a: 1 } в Set оба останутся — это разные ссылки.
Частые ошибки
- Квадратные скобки для Map.
map["a"] = 1создаёт обычное свойство, а не элемент Map:map.get("a")вернётundefined. Используйsetиget. lengthвместоsize. У Map и Set —size.- Ждать, что
Setуберёт одинаковые объекты.new Set([{a: 1}, {a: 1}]).size→2. - Забыли превратить в массив. У Set нет
mapиfilter— сначала[...set]. includesв цикле по большим массивам. Медленно. СделайSetи используйhas.
Шпаргалка
| Set | Map |
|---|---|
new Set(arr) |
new Map([[k, v], …]) |
s.add(x) |
m.set(k, v) |
s.has(x) |
m.has(k), m.get(k) |
s.delete(x) |
m.delete(k) |
s.size |
m.size |
[...s] — в массив |
[...m] — массив пар |
for (const x of s) |
for (const [k, v] of m) |