← JavaScript/Map и Set#73 из 383← ПредыдущийСледующий →+25 XP
Полезно по теме:Гайд: как учить JavaScriptПрактика: JS базаПрактика: async и сетьТермин: Closure

Map и Set

Реальная проблема: когда обычного объекта недостаточно

В Google Analytics нужно подсчитать, сколько раз каждый пользователь посетил страницу. Ключи — объекты пользователей, значения — счётчики. Обычный объект принимает только строковые ключи. Map решает это: ключом может быть что угодно — объект, функция, число. А Set идеально подходит когда нужен список уникальных значений без дубликатов.

Что решают Map и Set

  • Map: словарь с произвольными ключами, гарантированный порядок вставки, удобный API
  • Set: коллекция уникальных значений, быстрая проверка вхождения O(1), дедупликация
  • На основе предыдущих уроков

  • «Объекты» — Map как более мощная альтернатива объекту
  • «Массивы» — Set как массив без дубликатов
  • «Циклы» — for...of работает с Map и Set
  • Map — словарь с любыми ключами

    const map = new Map()
    
    // set/get/has/delete
    map.set('name', 'Алексей')
    map.set(42, 'числовой ключ')
    map.set({ id: 1 }, 'объект как ключ')
    map.get('name')     // 'Алексей'
    map.has(42)         // true
    map.delete(42)
    map.size            // 2
    
    // Инициализация из массива пар
    const prices = new Map([
      ['apple',  29],
      ['banana', 49],
      ['cherry', 199],
    ])

    Перебор Map:

    for (const [key, value] of prices) {
      console.log(`${key}: ${value} ₽`)
    }
    
    // Только ключи / значения
    [...prices.keys()]    // ['apple', 'banana', 'cherry']
    [...prices.values()]  // [29, 49, 199]
    [...prices.entries()] // [['apple', 29], ...]

    Map vs Object — когда что использовать:

    | Ситуация | Map | Object |

    |---|---|---|

    | Ключи — не строки | + | - |

    | Нужен .size | + | - |

    | Частые добавления/удаления | + | = |

    | JSON сериализация | - | + |

    | Простые данные | = | + |

    Set — множество уникальных значений

    const set = new Set()
    
    set.add('js')
    set.add('react')
    set.add('js')    // дубликат — игнорируется
    set.size         // 2
    
    set.has('react') // true
    set.delete('js')
    
    // Инициализация из массива — автоматически уникальные
    const unique = new Set([1, 2, 2, 3, 3, 3])
    console.log(unique.size)  // 3

    Конвертация Set ↔ Array:

    // Set → Array
    const arr = [...set]
    const arr2 = Array.from(set)
    
    // Array → уникальный Array
    const unique = [...new Set([1, 2, 2, 3])]  // [1, 2, 3]

    Типичные ошибки

    Ошибка 1: сравнение объектов-ключей по ссылке

    // Сломано:
    const map = new Map()
    map.set({ id: 1 }, 'user')
    map.get({ id: 1 })  // undefined! — другой объект, другая ссылка
    
    // Исправлено — используй примитив как ключ:
    map.set(1, 'user')
    map.get(1)  // 'user'

    Ошибка 2: JSON.stringify не сериализует Map

    // Сломано:
    const map = new Map([['a', 1]])
    JSON.stringify(map)  // '{}' — пустой объект!
    
    // Исправлено — конвертируй в массив:
    JSON.stringify([...map.entries()])  // '[["a",1]]'
    
    // Или в объект (если ключи — строки):
    JSON.stringify(Object.fromEntries(map))  // '{"a":1}'

    Ошибка 3: Set не дедуплицирует объекты

    // Объекты сравниваются по ссылке:
    const set = new Set()
    set.add({ id: 1 })
    set.add({ id: 1 })  // другой объект — добавится!
    console.log(set.size)  // 2
    
    // Set дедуплицирует только примитивы:
    new Set([1, 1, 2, '1'])  // Set {1, 2, '1'}

    В реальных проектах

  • Кэш запросов: Map<string, Response> — ключ: URL, значение: ответ
  • Права доступа: Set<string> — множество прав пользователя, O(1) проверка
  • Дедупликация тегов: [...new Set(allTags)] — убрать повторы
  • Подсчёт частоты: Map для подсчёта слов, категорий, событий
  • WeakMap/WeakSet: для хранения приватных данных объектов
  • Примеры

    Система аналитики — подсчёт событий и уникальных посетителей

    // Симуляция событий аналитики
    const events = [
      { page: '/home',    userId: 'u1', event: 'view' },
      { page: '/product', userId: 'u2', event: 'view' },
      { page: '/home',    userId: 'u1', event: 'view' },
      { page: '/product', userId: 'u3', event: 'purchase' },
      { page: '/home',    userId: 'u2', event: 'view' },
      { page: '/product', userId: 'u1', event: 'purchase' },
      { page: '/about',   userId: 'u4', event: 'view' },
    ]
    
    // Map: подсчёт просмотров по страницам
    const pageViews = new Map()
    for (const { page } of events) {
      pageViews.set(page, (pageViews.get(page) ?? 0) + 1)
    }
    console.log([...pageViews.entries()])
    // [['/home', 3], ['/product', 3], ['/about', 1]]
    
    // Set: уникальные посетители
    const uniqueUsers = new Set(events.map(e => e.userId))
    console.log('Уникальных посетителей:', uniqueUsers.size)  // 4
    
    // Set: страницы с покупками
    const purchasePages = new Set(
      events.filter(e => e.event === 'purchase').map(e => e.page)
    )
    console.log('Страницы с покупками:', [...purchasePages])  // ['/product']
    
    // Map: группировка событий по типу
    const byEvent = new Map()
    for (const evt of events) {
      if (!byEvent.has(evt.event)) byEvent.set(evt.event, [])
      byEvent.get(evt.event).push(evt.userId)
    }
    console.log('Покупатели:', [...new Set(byEvent.get('purchase'))])  // ['u3', 'u1']
    
    // Топ страниц (сортировка Map)
    const topPages = [...pageViews.entries()]
      .sort((a, b) => b[1] - a[1])
      .slice(0, 3)
    topPages.forEach(([page, views]) => {
      console.log(`${page}: ${views} просмотров`)
    })

    Map и Set

    Реальная проблема: когда обычного объекта недостаточно

    В Google Analytics нужно подсчитать, сколько раз каждый пользователь посетил страницу. Ключи — объекты пользователей, значения — счётчики. Обычный объект принимает только строковые ключи. Map решает это: ключом может быть что угодно — объект, функция, число. А Set идеально подходит когда нужен список уникальных значений без дубликатов.

    Что решают Map и Set

  • Map: словарь с произвольными ключами, гарантированный порядок вставки, удобный API
  • Set: коллекция уникальных значений, быстрая проверка вхождения O(1), дедупликация
  • На основе предыдущих уроков

  • «Объекты» — Map как более мощная альтернатива объекту
  • «Массивы» — Set как массив без дубликатов
  • «Циклы» — for...of работает с Map и Set
  • Map — словарь с любыми ключами

    const map = new Map()
    
    // set/get/has/delete
    map.set('name', 'Алексей')
    map.set(42, 'числовой ключ')
    map.set({ id: 1 }, 'объект как ключ')
    map.get('name')     // 'Алексей'
    map.has(42)         // true
    map.delete(42)
    map.size            // 2
    
    // Инициализация из массива пар
    const prices = new Map([
      ['apple',  29],
      ['banana', 49],
      ['cherry', 199],
    ])

    Перебор Map:

    for (const [key, value] of prices) {
      console.log(`${key}: ${value} ₽`)
    }
    
    // Только ключи / значения
    [...prices.keys()]    // ['apple', 'banana', 'cherry']
    [...prices.values()]  // [29, 49, 199]
    [...prices.entries()] // [['apple', 29], ...]

    Map vs Object — когда что использовать:

    | Ситуация | Map | Object |

    |---|---|---|

    | Ключи — не строки | + | - |

    | Нужен .size | + | - |

    | Частые добавления/удаления | + | = |

    | JSON сериализация | - | + |

    | Простые данные | = | + |

    Set — множество уникальных значений

    const set = new Set()
    
    set.add('js')
    set.add('react')
    set.add('js')    // дубликат — игнорируется
    set.size         // 2
    
    set.has('react') // true
    set.delete('js')
    
    // Инициализация из массива — автоматически уникальные
    const unique = new Set([1, 2, 2, 3, 3, 3])
    console.log(unique.size)  // 3

    Конвертация Set ↔ Array:

    // Set → Array
    const arr = [...set]
    const arr2 = Array.from(set)
    
    // Array → уникальный Array
    const unique = [...new Set([1, 2, 2, 3])]  // [1, 2, 3]

    Типичные ошибки

    Ошибка 1: сравнение объектов-ключей по ссылке

    // Сломано:
    const map = new Map()
    map.set({ id: 1 }, 'user')
    map.get({ id: 1 })  // undefined! — другой объект, другая ссылка
    
    // Исправлено — используй примитив как ключ:
    map.set(1, 'user')
    map.get(1)  // 'user'

    Ошибка 2: JSON.stringify не сериализует Map

    // Сломано:
    const map = new Map([['a', 1]])
    JSON.stringify(map)  // '{}' — пустой объект!
    
    // Исправлено — конвертируй в массив:
    JSON.stringify([...map.entries()])  // '[["a",1]]'
    
    // Или в объект (если ключи — строки):
    JSON.stringify(Object.fromEntries(map))  // '{"a":1}'

    Ошибка 3: Set не дедуплицирует объекты

    // Объекты сравниваются по ссылке:
    const set = new Set()
    set.add({ id: 1 })
    set.add({ id: 1 })  // другой объект — добавится!
    console.log(set.size)  // 2
    
    // Set дедуплицирует только примитивы:
    new Set([1, 1, 2, '1'])  // Set {1, 2, '1'}

    В реальных проектах

  • Кэш запросов: Map<string, Response> — ключ: URL, значение: ответ
  • Права доступа: Set<string> — множество прав пользователя, O(1) проверка
  • Дедупликация тегов: [...new Set(allTags)] — убрать повторы
  • Подсчёт частоты: Map для подсчёта слов, категорий, событий
  • WeakMap/WeakSet: для хранения приватных данных объектов
  • Примеры

    Система аналитики — подсчёт событий и уникальных посетителей

    // Симуляция событий аналитики
    const events = [
      { page: '/home',    userId: 'u1', event: 'view' },
      { page: '/product', userId: 'u2', event: 'view' },
      { page: '/home',    userId: 'u1', event: 'view' },
      { page: '/product', userId: 'u3', event: 'purchase' },
      { page: '/home',    userId: 'u2', event: 'view' },
      { page: '/product', userId: 'u1', event: 'purchase' },
      { page: '/about',   userId: 'u4', event: 'view' },
    ]
    
    // Map: подсчёт просмотров по страницам
    const pageViews = new Map()
    for (const { page } of events) {
      pageViews.set(page, (pageViews.get(page) ?? 0) + 1)
    }
    console.log([...pageViews.entries()])
    // [['/home', 3], ['/product', 3], ['/about', 1]]
    
    // Set: уникальные посетители
    const uniqueUsers = new Set(events.map(e => e.userId))
    console.log('Уникальных посетителей:', uniqueUsers.size)  // 4
    
    // Set: страницы с покупками
    const purchasePages = new Set(
      events.filter(e => e.event === 'purchase').map(e => e.page)
    )
    console.log('Страницы с покупками:', [...purchasePages])  // ['/product']
    
    // Map: группировка событий по типу
    const byEvent = new Map()
    for (const evt of events) {
      if (!byEvent.has(evt.event)) byEvent.set(evt.event, [])
      byEvent.get(evt.event).push(evt.userId)
    }
    console.log('Покупатели:', [...new Set(byEvent.get('purchase'))])  // ['u3', 'u1']
    
    // Топ страниц (сортировка Map)
    const topPages = [...pageViews.entries()]
      .sort((a, b) => b[1] - a[1])
      .slice(0, 3)
    topPages.forEach(([page, views]) => {
      console.log(`${page}: ${views} просмотров`)
    })

    Задание

    В мессенджере нужно реализовать систему онлайн-статусов. Напиши функцию createPresenceManager(), которая возвращает объект с методами: setOnline(userId) — отметить пользователя онлайн, setOffline(userId) — отметить офлайн, isOnline(userId) — проверить статус, getOnlineCount() — количество онлайн, getOnlineUsers() — массив ID онлайн-пользователей. Используй Set для хранения онлайн-пользователей.

    Подсказка

    setOnline: onlineUsers.add(userId). setOffline: onlineUsers.delete(userId). isOnline: onlineUsers.has(userId). getOnlineUsers: [...onlineUsers].

    Загружаем среду выполнения...
    Загружаем AI-помощника...