Map и Set в JavaScript

Map и Set в JavaScript #

Map и Set — встроенные коллекции ES2015: Map хранит пары ключ–значение с ключами любого типа, Set — только уникальные значения. Они дополняют обычные объекты {} и массивы, когда важны частые добавления/удаления, предсказуемый размер или гарантия уникальности (теги трека, id курьеров, кэш ETA).


Set: уникальные значения #

const tags = new Set(['focus', 'work', 'focus', 'instrumental'])

tags.size // 3
tags.has('work') // true
tags.add('night')
tags.delete('work')

Порядок итерации — порядок вставки.

Преобразование массива в уникальный список:

const rawTags = ['pop', 'work', 'pop', 'jazz', 'work']
const unique = [...new Set(rawTags)]
// ["pop", "work", "jazz"]

Проверка дубликата sku в корзине без объекта-словаря:

function hasDuplicateSkus(lines) {
  const seen = new Set()
  for (const line of lines) {
    if (seen.has(line.sku)) return true
    seen.add(line.sku)
  }
  return false
}

Перебор Set #

const zones = new Set(['north', 'center', 'south'])

for (const zone of zones) {
  console.log(zone)
}

zones.forEach((zone) => console.log(zone))

const list = [...zones] // массив из Set

Map: словарь с любыми ключами #

const etaByZone = new Map()

etaByZone.set('center', 25)
etaByZone.set('suburb', 45)
etaByZone.get('center') // 25
etaByZone.has('north') // false
etaByZone.delete('suburb')
etaByZone.size // 1

Ключом может быть объект (по ссылке), число, символ:

const feeByTier = new Map([
  ['standard', 199],
  ['express', 399],
])

feeByTier.get('express') // 399

Инициализация из массива пар — удобно для статических таблиц тарифов.


Map vs обычный Object #

КритерийMap{}
Ключилюбые значениястроки и символы
Порядокпорядок вставкичастично гарантирован для строковых ключей
Размерmap.sizeвручную Object.keys(obj).length
Частые add/deleteоптимизированменее предсказуемо
JSONне сериализуется напрямуюда

Для JSON API по-прежнему объекты; Map — для runtime-кэша в памяти (например, расчёт доставки по зонам в сессии чекаута).


Итерация Map #

const trackById = new Map([
  ['t1', { title: 'Intro' }],
  ['t2', { title: 'Main' }],
])

for (const [id, track] of trackById) {
  console.log(id, track.title)
}

for (const id of trackById.keys()) {
  console.log(id)
}

for (const track of trackById.values()) {
  console.log(track.title)
}

const entries = [...trackById.entries()]

Преобразования Map ↔ Object ↔ Array #

Object → Map:

const tariffs = { standard: 199, express: 399 }
const tariffMap = new Map(Object.entries(tariffs))

Map → Object (осторожно с нестроковыми ключами):

const plain = Object.fromEntries(tariffMap)
// { standard: 199, express: 399 }

Set из массива и обратно — см. выше через spread.


WeakMap и WeakSet (кратко) #

WeakMap и WeakSet хранят слабые ссылки на объекты-ключи: если на объект больше нет ссылок, запись может быть собрана сборщиком мусора. Нет итерации и size.

Типичное применение — приватные метаданные DOM-элементов или кэш, привязанный к жизненному циклу объекта. В учебных примерах корзины чаще достаточно обычного Map.


Практика: кэш расчёта доставки #

const deliveryCache = new Map()

function etaMinutes(zone, loadFactor) {
  const key = `${zone}:${loadFactor}`
  if (deliveryCache.has(key)) {
    return deliveryCache.get(key)
  }

  const base = zone === 'center' ? 20 : 40
  const minutes = Math.round(base * loadFactor)
  deliveryCache.set(key, minutes)
  return minutes
}

etaMinutes('center', 1.2) // считает и кэширует
etaMinutes('center', 1.2) // из кэша

Практика: уникальные исполнители в плейлисте #

const playlist = [
  { title: 'A', artist: 'Lumen' },
  { title: 'B', artist: 'Echo' },
  { title: 'C', artist: 'Lumen' },
]

const artists = [...new Set(playlist.map((t) => t.artist))]
// ["Lumen", "Echo"]
function countByCategory(lines) {
  const counts = new Map()
  for (const line of lines) {
    const prev = counts.get(line.category) ?? 0
    counts.set(line.category, prev + 1)
  }
  return counts
}

countByCategory([
  { category: 'gear', sku: 'a' },
  { category: 'digital', sku: 'b' },
  { category: 'gear', sku: 'c' },
])
// Map { 'gear' => 2, 'digital' => 1 }

Практика: быстрый поиск по id #

function indexById(items) {
  return new Map(items.map((item) => [item.id, item]))
}

const scoreIndex = indexById([
  { id: 'u1', score: 100 },
  { id: 'u2', score: 250 },
])

scoreIndex.get('u2') // { id: 'u2', score: 250 }

Для небольших списков достаточно find; Map выигрывает при частых lookup и большом числе записей.


Итог #

  • Set хранит уникальные значения; [...new Set(arr)] — быстрый способ убрать дубликаты.
  • Map — пары ключ–значение с любыми ключами и свойством size.
  • Для JSON и контрактов API используйте объекты; Map/Set — для внутренней логики приложения.
  • Итерация for...of по Map даёт пары [key, value].
  • WeakMap/WeakSet — ниша для слабых ссылок без перебора всех ключей.

См. также #