JavaScript Set
и Map
: За пределами массивов и объектов
До того как в ES6 появились Set
и Map
, возможности хранения коллекций данных в JavaScript были ограничены. Использовались объекты для пар ключ-значение и массивы для списков. Это приводило к распространённым проблемам:
// Проблема 1: Использование массивов для уникальных значений
const userIds = [1, 2, 2, 3]
const uniqueIds = userIds.filter((id, index) =>
userIds.indexOf(id) === index
)
console.log(uniqueIds)
// [1, 2, 3] ✅ Правильно, но неэффективно!
// Приходится многократно просматривать массив
// Проблема 2: Коллизии чисел и строк в качестве ключей
const cache = {}
cache[123] = "data" // Использование числа в качестве ключа
cache["123"] = "other data" // Использование строки в качестве ключа
console.log(cache[123]) ❌ // "other data" - оба ключа преобразуются в строку "123"
// Проблема 3: Объекты в качестве ключей не работают
const userMap = {}
const user = {id: 1}
userMap[user] = "data"
console.log(userMap[user]) // "data"
console.log(userMap["[object Object]"]) ❌ // "data" - то же значение!
// Оба объекта становятся "[object Object]".
const user2 = {id: 2}
userMap[user2] = "different data"
console.log(userMap[user]) ❌ // "different data" - первое значение было перезаписано!
Что такое Set
и Map
Set
и Map
— специализированные структуры данных в JavaScript, каждая предназначена для решения специфических задач, с которыми плохо справляются массивы и объекты.
Set
— коллекция уникальных значений. Думайте о нём как о сумке, автоматически удаляющей дубликаты.
При добавлении одного и того же значения в Set
дважды сохраняется только одна копия. Он прекрасно подходит для создания списков, в которых каждый элемент должен встречаться только один раз.
// Set автоматически обрабатывает уникальность
const uniqueNumbers = new Set([1, 1, 2, 2, 3])
console.log(uniqueNumbers) // ✅ Set(3) {1, 2, 3}
// Быстрый поиск при проверке существования
uniqueNumbers.has(1) // true
uniqueNumbers.has(4) // false
// Простое добавление и удаление элементов
uniqueNumbers.add(4) // Set(4) {1, 2, 3, 4}
uniqueNumbers.delete(1) // Set(3) {2, 3, 4}
Map
— коллекция пар ключ-значение, где ключи могут быть любого типа — числа, строки, объекты, даже функции. В отличие от объектов, преобразующих все ключи в строки, Map
сохраняет тип ключа. Это делает их идеальными для создания словарей или кэшей, где тип ключа имеет значение:
// Map поддерживает типы ключей
const userProfiles = new Map()
// Числа остаются числами
userProfiles.set(123, "John")
userProfiles.set("123", "Jane") // Отличается от 123
// Объекты могут быть ключами
const user = {id: 1}
userProfiles.set(user, "User data")
console.log(userProfiles.get(123)) // "John"
console.log(userProfiles.get("123")) // "Jane"
console.log(userProfiles.get(user)) // "User data"
Так, Set
и Map
решают две проблемы, рассмотренные ранее при работе с массивами и объектами.
// Set для уникальных значений
const userIds = new Set([1, 2, 2, 3]) // Автоматическая уникальность
console.log(userIds) // Set(3) {1, 2, 3}
// Map для корректного хранения ключ-значение
const cache = new Map()
cache.set(123, "data") // Числовой ключ
cache.set("123", "other") // Строковый ключ - другой!
const userMap = new Map()
const user = {id: 1}
userMap.set(user, "data") // Объект в качестве ключа - работает!
Set
и Map
отлично справляются с отношениями данных, кэшированием и проверкой уникальности. У каждого из них есть определённые сценарии использования, позволяющие превзойти традиционные массивы и объекты.
Когда использовать Set
Используйте Set
, если требуется быстрый поиск и гарантия уникальности данных. В системе тегов можно мгновенно проверить, есть ли в статье определённый тег, не перебирая массив.
// Быстрая проверка существования
const adminPermissions = new Set(['edit', 'delete', 'create'])
if (adminPermissions.has('delete')) { // Мгновенный поиск
deleteResource()
}
// Отслеживание уникальных значений
const uniqueVisitors = new Set()
uniqueVisitors.add(userId) // Автоматическая обработка дубликатов
Когда использовать Map
Map
— идеальный вариант, если нужно связать данные с любым типом ключа — например, кэшировать ответы API по URL, сохранять предпочтения пользователей или поддерживать связь между элементами DOM и их данными.
// Типобезопасные ключи
const apiCache = new Map()
apiCache.set('/api/users', userData) // URL в виде точной строки
apiCache.set(404, errorData) // Число остаётся числом
// Objects as keys
const elementStates = new Map()
const button = document.querySelector('#submit')
elementStates.set(button, { // DOM элемент в качестве ключа
clicks: 0,
lastClicked: null
})
Компромиссы производительности
Set
и Map
обеспечивают O(1) операций благодаря реализации хэш-таблиц, по сравнению с массивами, имеющие O(n) для поиска, и объектами, преобразующими ключи в строки.
Однако за эту скорость приходится платить: Set
и Map
используют больше памяти, чем массивы и объекты. Структура хэш-таблицы, обеспечивающая их быстрые операции, требует дополнительных затрат памяти на обслуживание.
В частности, для Set
хэш-таблица обеспечивает мгновенное обнаружение дубликатов, а не требует полного сканирования массива. Map
достигают своей производительности за счёт использования аналогичной структуры, но хранят и ключ, и значение в хэш-таблице.
// Производительность Set
const set = new Set()
set.add(value) // O(1) - постоянное время вычисления хэша
set.has(value) // O(1) - прямой поиск по хэш-таблице
set.delete(value) // O(1) - немедленное удаление из хэш-таблицы
set.size // O(1) - поддерживается счётчик размеров
// Производительность Map
const map = new Map()
map.set(key, value) // O(1) - вставка в хэш-таблицу
map.get(key) // O(1) - прямой поиск по хэшу
map.has(key) // O(1) - проверка существования в хэше
map.delete(key) // O(1) - удаление из хэш-таблицы
map.size // O(1) - постоянное отслеживание размера
Set
и Map
отлично справляются с пользовательскими сессиями. Set
обеспечивает мгновенный поиск O(1), проверяющий, вошёл ли пользователь в систему, что гораздо быстрее, чем поиск по массиву пользовательских ID. Map
позволяет хранить данные сессий с любым типом идентификатора пользователя (число, строка или объект) без проблем с преобразованием типов ключей.
class SessionManager {
constructor() {
this.activeSessions = new Map() // userId -> данные сессии
this.loggedInUsers = new Set() // уникальные ID пользователей
}
login(userId, sessionData) {
this.loggedInUsers.add(userId) // O(1) добавление
this.activeSessions.set(userId, {
data: sessionData,
startTime: Date.now()
})
}
isLoggedIn(userId) {
return this.loggedInUsers.has(userId) // O(1) проверка
}
getSessionData(userId) {
return this.activeSessions.get(userId) // O(1) поиск
}
}
Пример SessionManager
демонстрирует, как Set
может отслеживать уникальных активных пользователей, в то время как Map
хранит подробные данные о сеансе. Эта комбинация эффективна, поскольку разделяет задачи: Set
обрабатывает уникальность, а Map
— ассоциацию данных.
Мгновенное время поиска делает эту комбинацию идеальной для приложений с высокой посещаемостью, где производительность имеет решающее значение.
Когда не стоит использовать Set
и Map
Set
и Map
требуют больших затрат памяти из-за структуры хэш-таблиц. Для небольших коллекций или простых строковых ключей более эффективными могут быть массивы и объекты.
// 1. Нужны методы массива
const numbers = [1, 2, 3, 4]
numbers.map(n => n * 2) // Это невозможно сделать с Set
numbers.filter(n => n > 2) // В Set нет встроенной фильтрации
numbers.reduce((a, b) => a + b) // В Set нет уменьшения
// 2. Нужен доступ к индексу
const items = ['first', 'second', 'third']
console.log(items[0]) // Прямой доступ к индексу
console.log(items.indexOf('second')) // Позиция важна
// 3. Нужна сериализация JSON
const user = {name: 'John', age: 30}
JSON.stringify(user) // Работает отлично
JSON.stringify(new Map()) // Пустой объект {}
JSON.stringify(new Set()) // Пустой массив []
// 4. Нужны простые только строковые ключи
// Использование Map будет излишним
const config = {
apiKey: '123',
baseUrl: 'api.example.com'
}
Set
и Map
— не просто альтернатива массивам и объектам, это специализированные инструменты, делающие конкретные паттерны программирования более эффективными и надёжными. Используйте их, когда уникальные характеристики соответствуют потребностям, и вы будете писать более надёжный и производительный код.