В чём разница между Map, Set и Object в JavaScript?
Вы рефакторите компонент и вам нужно хранить пары ключ-значение. По привычке вы тянетесь к объекту, но останавливаетесь. Может быть, лучше использовать Map? Или здесь подойдёт Set? Это решение важнее, чем кажется — каждая структура имеет свои характеристики производительности и семантическое значение, которые влияют на ясность и эффективность вашего кода.
Это сравнение структур данных JavaScript разбирает практические различия между Map, Set и Object, чтобы вы могли сделать правильный выбор в production-коде.
Ключевые выводы
- Объекты приводят ключи к строкам, в то время как Map сохраняет типы ключей в точности — это различие предотвращает тонкие ошибки коллизий.
- Map превосходит Object по производительности при частых добавлениях и удалениях, тогда как Object отлично справляется с нагрузками, где преобладает чтение, при стабильной структуре.
- Set обеспечивает проверку принадлежности за O(1) и теперь поставляется с нативными методами
union,intersectionиdifferenceв современном JavaScript. - По умолчанию используйте Object для структурированных данных, обращайтесь к Map, когда ключи динамические или не строковые, и используйте Set, когда уникальность — ваша основная задача.
Основное назначение: когда использовать Map, Set или Object
Разница между Map и Object в JavaScript сводится к назначению и обработке ключей:
- Object: Структурированные данные с известными строковыми/символьными ключами. Например, конфигурация, props компонентов, ответы API.
- Map: Динамические коллекции ключ-значение, где ключи могут быть любого типа. Например, кэши, таблицы поиска с объектными ключами, частые добавления и удаления.
- Set: Коллекции уникальных значений без ключей. Например, дедупликация, отслеживание просмотренных элементов, проверки принадлежности.
Обработка ключей: фундаментальная разница между Map и Object
Объекты приводят ключи к строкам. Map сохраняет типы ключей в точности.
const obj = {}
obj[1] = 'number'
obj['1'] = 'string'
console.log(Object.keys(obj)) // ['1'] — коллизия
const map = new Map()
map.set(1, 'number')
map.set('1', 'string')
console.log(map.size) // 2 — различные ключи
Map также принимает объекты в качестве ключей — то, что объекты принципиально не могут делать без обходных путей с сериализацией.
const userCache = new Map()
const user = { id: 42 }
userCache.set(user, { lastSeen: Date.now() })
См. документацию MDN для Map для полной семантики ключей.
Семантика итерации и порядка
Все три структуры имеют определённый порядок итерации, но семантика различается:
Map и Set: Чистый порядок вставки. Всегда.
Object: Порядок вставки для строковых ключей, но ключи с целочисленными индексами (например, '1', '42') сортируются численно первыми. Это застаёт разработчиков врасплох:
const obj = { b: 1, 2: 2, a: 3, 1: 4 }
console.log(Object.keys(obj)) // ['1', '2', 'b', 'a']
Map и Set напрямую итерируемы с помощью for...of. Объекты требуют Object.keys(), Object.values() или Object.entries().
Характеристики производительности в масштабе
Что касается вопроса производительности JavaScript Map vs Set vs Object, контекст имеет значение:
Объекты превосходны при нагрузках с преобладанием чтения со строковыми ключами. Современные движки, такие как V8, оптимизируют доступ к свойствам через скрытые классы, делая чтение очень быстрым — когда форма объекта остаётся стабильной.
Map выигрывает при частых добавлениях и удалениях. Удаление свойств объекта (через delete) может вызвать деоптимизацию, инвалидируя скрытые классы. Спецификация ECMAScript гарантирует сублинейное среднее время доступа для Map, не предписывая конкретную внутреннюю реализацию.
Set обеспечивает проверку принадлежности за O(1) через has(), превосходя по производительности includes() массива для любой нетривиальной коллекции.
Discover how at OpenReplay.com.
Нативные операции Set
Set теперь включает встроенные методы для общих операций (см. документацию MDN для Set):
const a = new Set([1, 2, 3])
const b = new Set([2, 3, 4])
a.union(b) // Set {1, 2, 3, 4}
a.intersection(b) // Set {2, 3}
a.difference(b) // Set {1}
a.symmetricDifference(b) // Set {1, 4}
a.isSubsetOf(b) // false
Эти методы широко поддерживаются в современных браузерах.
Практические frontend-сценарии
Используйте Object, когда:
- Определяете props компонентов или конфигурацию
- Работаете с JSON-сериализацией (Map не сериализуются напрямую)
- Ключи известны на момент написания кода
Используйте Map, когда:
- Ключи динамические или предоставлены пользователем (избегает загрязнения прототипа)
- Вам нужны ссылки на объекты в качестве ключей
- Важно отслеживание размера (
map.sizevsObject.keys(obj).length) - Создаёте кэши с частыми обновлениями
Используйте Set, когда:
- Отслеживаете уникальные ID или просмотренные элементы
- Дедуплицируете массивы:
[...new Set(array)] - Нужна быстрая проверка принадлежности
Краткая справка
| Характеристика | Object | Map | Set |
|---|---|---|---|
| Типы ключей | string, symbol | любые | Н/П (только значения) |
| Размер | Object.keys().length | .size | .size |
| Итерация | косвенная | прямая | прямая |
| Поддержка JSON | нативная | ручная | ручная |
| Риск прототипа | да | нет | нет |
Заключение
Выбор между Map, Set и Object — это не вопрос о том, что “лучше”, а о соответствии структуры данных вашему случаю использования. Объекты остаются правильным выбором для структурированных данных со строковыми ключами. Map чисто обрабатывает динамические сценарии ключ-значение. Set эффективно решает проблемы уникальности.
По умолчанию используйте объекты для простоты. Обращайтесь к Map, когда вам нужны его специфические возможности. Используйте Set, когда уникальность — ваша основная задача.
Часто задаваемые вопросы
Не напрямую. JSON.stringify не обрабатывает Map нативно. Сначала нужно преобразовать Map в массив записей, используя Array.from(map) или оператор spread, затем stringify этот массив. Для восстановления распарсите JSON и передайте полученный массив в конструктор Map: new Map(JSON.parse(jsonString)).
Map обрабатывает частые вставки и удаления без деоптимизации, которую может вызвать удаление свойств объекта. Они также отслеживают размер нативно через свойство size, принимают любой тип значения в качестве ключа, включая DOM-узлы или ссылки на объекты, и не несут риска загрязнения прототипа от предоставленных пользователем ключей.
Эти методы теперь широко поддерживаются в современных браузерах. Если вам нужно поддерживать старые окружения, проверьте таблицы совместимости или используйте полифилл.
Используйте массив, когда важен порядок вставки с дубликатами, когда вам нужен доступ по индексу, или когда коллекция достаточно мала, чтобы накладные расходы Set не были оправданы. Для больших коллекций, где уникальность и быстрый поиск являются приоритетами, Set — лучший выбор.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.