Map и Set

коллекции≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Массив хранит всё подряд и молча терпит дубликаты, а у объекта ключ — строка или символ. Set и Map закрывают ровно эти два пробела: коллекция без повторов и словарь, где ключом может быть что угодно.

Разделы темы:·

Set — это множество: набор значений, где каждое встречается ровно один раз. Добавить одно и то же дважды никто не запрещает, просто во второй раз ничего не произойдёт. Основные действия — add, has, delete и свойство size.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Сравнение «уже есть?» строгое, как у ===, с одной поблажкой: NaN считается равным самому себе (в обычном сравнении это не так). Число 5 и строка "5" — разные значения. А два объекта с одинаковыми полями — тоже разные: Set смотрит не на содержимое, а на то, один ли это объект в памяти.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Map — словарь пар «ключ → значение». От объекта его отличает главное: ключом может быть что угодно — число, булево, функция и, самое ценное, другой объект. Это позволяет привязывать данные к объекту, не трогая его самого.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Набор методов маленький и предсказуемый: set(ключ, значение) кладёт пару, get достаёт значение (или undefined, если пары нет), has отвечает да/нет, delete убирает пару, clear — все сразу. set возвращает саму карту, поэтому вызовы можно ставить цепочкой.

У обычного объекта ключи — всегда строки (или символы). Напишете obj[1] — единица молча станет "1", а obj[true] превратится в "true". Map ничего не преобразует и хранит ключ ровно таким, каким его дали.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Объект наследует свойства от прототипа, поэтому проверка in находит то, чего вы туда не клали. Пустая Map действительно пуста.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Третье отличие бытовое, но приятное: у Map размер лежит в свойстве size, а у объекта его приходится вычислять через Object.keys(obj).length — то есть каждый раз строить временный массив ключей.

Обе коллекции перебираются через for..of и хранят порядок вставки: что положили первым, то первым и придёт. Map на каждом шаге отдаёт пару-массив [ключ, значение] — её удобно сразу разложить деструктуризацией.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

У Map есть три «среза»: keys(), values() и entries(). У Set те же методы существуют для совместимости, только keys() и values() там — одно и то же. Размер у обеих коллекций — свойство size, без скобок; забудете и напишете size() — получите ошибку.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Объект с числовыми ключами при переборе сортирует их по возрастанию, независимо от того, в каком порядке вы их добавляли. Map всегда сохраняет порядок вставки. Если порядок для вас важен — это ещё один довод в пользу Map.

Правило грубое, но рабочее: нужна проверка «есть ли такое значение» — берите Set; нужны пары с нестроковыми ключами или частые добавления и удаления — Map; всё остальное прекрасно живёт в массиве и объекте.

Что нужноЧто братьПочему
Только уникальные значенияSetповторы отсекаются сами, has не перебирает всё подряд
Ключи — объекты или числаMapобъект превратил бы их в строки
Данные приходят и уходят в JSONобычный объектJSON.stringify не умеет сериализовать Map и Set
Часто добавляем и удаляем парыMapsize под рукой, порядок вставки сохраняется
Доступ по индексу, сортировка, срезымассиву Set и Map индексов нет вообще

Переходы между структурами дешёвые: new Set(массив) убирает дубликаты, [...set] возвращает массив, new Map(Object.entries(obj)) делает карту из объекта, а Object.fromEntries(map) — объект из карты. Так что выбор не навсегда.

У обычной Map есть побочный эффект: пока карта жива, живы и все её ключи. Объект, который вы положили ключом, не будет удалён сборщиком мусора, даже если во всей программе на него больше никто не ссылается. WeakMap держит ключи «слабо» — не мешает объекту исчезнуть, и пара пропадает вместе с ним.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Ограничения строгие: ключами могут быть только объекты (и символы), перебора и 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), которая возвращает новый массив без повторов, сохраняя порядок первых появлений. Исходный массив менять нельзя.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Set сам отбрасывает повторы и хранит порядок вставки — остаётся развернуть его обратно в массив спред-оператором.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Вариант через filter с indexOf тоже работает, но для каждого элемента он заново пробегает массив — на больших данных это заметно медленнее. И не забывайте: Set сравнивает объекты по ссылке, поэтому два разных объекта с одинаковыми полями останутся оба.

Счётчик слов

Напишите countWords(list): функция принимает массив слов и возвращает Map, где ключ — слово, а значение — сколько раз оно встретилось.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Идём по массиву и на каждом шаге берём текущее число: если пары ещё нет, get вернёт undefined, и || 0 подставит ноль.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Map здесь удобнее объекта по двум причинам: не мешаются унаследованные ключи вроде toString, и порядок пар совпадает с порядком первого появления слова.