Map и Set
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Массив хранит всё подряд и молча терпит дубликаты, а у объекта ключ — строка или символ. Set и Map закрывают ровно эти два пробела: коллекция без повторов и словарь, где ключом может быть что угодно.
Set — это множество: набор значений, где каждое встречается ровно один раз. Добавить одно и то же дважды никто не запрещает, просто во второй раз ничего не произойдёт. Основные действия — add, has, delete и свойство size.
Сравнение «уже есть?» строгое, как у ===, с одной поблажкой: NaN считается равным самому себе (в обычном сравнении это не так). Число 5 и строка "5" — разные значения. А два объекта с одинаковыми полями — тоже разные: Set смотрит не на содержимое, а на то, один ли это объект в памяти.
Map — словарь пар «ключ → значение». От объекта его отличает главное: ключом может быть что угодно — число, булево, функция и, самое ценное, другой объект. Это позволяет привязывать данные к объекту, не трогая его самого.
Набор методов маленький и предсказуемый: set(ключ, значение) кладёт пару, get достаёт значение (или undefined, если пары нет), has отвечает да/нет, delete убирает пару, clear — все сразу. set возвращает саму карту, поэтому вызовы можно ставить цепочкой.
У обычного объекта ключи — всегда строки (или символы). Напишете obj[1] — единица молча станет "1", а obj[true] превратится в "true". Map ничего не преобразует и хранит ключ ровно таким, каким его дали.
Объект наследует свойства от прототипа, поэтому проверка in находит то, чего вы туда не клали. Пустая Map действительно пуста.
Третье отличие бытовое, но приятное: у Map размер лежит в свойстве size, а у объекта его приходится вычислять через Object.keys(obj).length — то есть каждый раз строить временный массив ключей.
Обе коллекции перебираются через for..of и хранят порядок вставки: что положили первым, то первым и придёт. Map на каждом шаге отдаёт пару-массив [ключ, значение] — её удобно сразу разложить деструктуризацией.
У Map есть три «среза»: keys(), values() и entries(). У Set те же методы существуют для совместимости, только keys() и values() там — одно и то же. Размер у обеих коллекций — свойство size, без скобок; забудете и напишете size() — получите ошибку.
Объект с числовыми ключами при переборе сортирует их по возрастанию, независимо от того, в каком порядке вы их добавляли. Map всегда сохраняет порядок вставки. Если порядок для вас важен — это ещё один довод в пользу Map.
Правило грубое, но рабочее: нужна проверка «есть ли такое значение» — берите Set; нужны пары с нестроковыми ключами или частые добавления и удаления — Map; всё остальное прекрасно живёт в массиве и объекте.
| Что нужно | Что брать | Почему |
|---|---|---|
| Только уникальные значения | Set | повторы отсекаются сами, has не перебирает всё подряд |
| Ключи — объекты или числа | Map | объект превратил бы их в строки |
| Данные приходят и уходят в JSON | обычный объект | JSON.stringify не умеет сериализовать Map и Set |
| Часто добавляем и удаляем пары | Map | size под рукой, порядок вставки сохраняется |
| Доступ по индексу, сортировка, срезы | массив | у Set и Map индексов нет вообще |
Переходы между структурами дешёвые: new Set(массив) убирает дубликаты, [...set] возвращает массив, new Map(Object.entries(obj)) делает карту из объекта, а Object.fromEntries(map) — объект из карты. Так что выбор не навсегда.
У обычной Map есть побочный эффект: пока карта жива, живы и все её ключи. Объект, который вы положили ключом, не будет удалён сборщиком мусора, даже если во всей программе на него больше никто не ссылается. WeakMap держит ключи «слабо» — не мешает объекту исчезнуть, и пара пропадает вместе с ним.
Ограничения строгие: ключами могут быть только объекты (и символы), перебора и size нет — содержимое может измениться в любой момент, поэтому показывать его было бы нечестно. Из методов остаются get, set, has, delete. Есть и WeakSet — то же самое, но для значений. Новичку они нужны редко: типичные случаи — кеш результатов и служебные пометки к чужим объектам.
- Set — коллекция уникальных значений: add, has, delete, size; повторное добавление просто ничего не меняет, а сравнение строгое, только NaN равен самому себе
- Map — словарь, где ключом может быть что угодно, включая объекты: set, get, has, delete, size
- Обычный объект приводит ключи к строкам и тащит за собой прототип; Map хранит ключ как есть и знает свой размер без Object.keys
- Обе коллекции перебираются for..of в порядке вставки; Map на каждом шаге отдаёт пару [ключ, значение]
- WeakMap держит ключи-объекты слабо: ни size, ни перебора, зато не мешает сборщику мусора освободить память
1/3Что выведет console.log(new Set([1, "1", 1, NaN, NaN]).size)?
2/3Почему объект не подходит, если ключами должны быть объекты?
3/3Что верно про WeakMap?
Уникальные значения массива
Напишите функцию unique(arr), которая возвращает новый массив без повторов, сохраняя порядок первых появлений. Исходный массив менять нельзя.
Set сам отбрасывает повторы и хранит порядок вставки — остаётся развернуть его обратно в массив спред-оператором.
Вариант через filter с indexOf тоже работает, но для каждого элемента он заново пробегает массив — на больших данных это заметно медленнее. И не забывайте: Set сравнивает объекты по ссылке, поэтому два разных объекта с одинаковыми полями останутся оба.
Счётчик слов
Напишите countWords(list): функция принимает массив слов и возвращает Map, где ключ — слово, а значение — сколько раз оно встретилось.
Идём по массиву и на каждом шаге берём текущее число: если пары ещё нет, get вернёт undefined, и || 0 подставит ноль.
Map здесь удобнее объекта по двум причинам: не мешаются унаследованные ключи вроде toString, и порядок пар совпадает с порядком первого появления слова.