Map (Словарь/Ассоциативный массив)
Map — коллекция пар ключ-значение, где ключом может быть любое значение (не только строка). Обеспечивает O(1) доступ по ключу.
Зачем нужно
Map решает задачу «найти значение по ключу» за O(1). Кэширование, подсчёт частоты, группировка, индексация — Map быстрее объекта для этих задач и поддерживает любые типы ключей.
Где используется
- Кэширование (мемоизация)
- Подсчёт частоты элементов
- Хранение метаданных для объектов
- Маршрутизация (URL → handler)
- Локализация (ключ → перевод)
- Конфигурации и реестры
Предпосылки
Map в JavaScript
const map = new Map();
// Установка — O(1)
map.set('name', 'Alice');
map.set(42, 'число');
map.set(true, 'булево');
const obj = { id: 1 };
map.set(obj, 'объект как ключ'); // любой тип ключа!
// Получение — O(1)
map.get('name'); // 'Alice'
map.get(42); // 'число'
map.get(obj); // 'объект как ключ'
// Проверка — O(1)
map.has('name'); // true
// Удаление — O(1)
map.delete('name'); // true
// Размер — O(1)
map.size; // 3
// Очистка
map.clear();
// Создание из массива пар
const fromPairs = new Map([
['a', 1],
['b', 2],
['c', 3],
]);
Map vs Object
| Аспект | Map | Object |
|---|---|---|
| Тип ключей | Любой (объект, функция, число) | Строки и Symbol |
| Порядок | Гарантирован (по вставке) | Для строк — по вставке |
| Размер | map.size (O(1)) |
Object.keys(obj).length (O(n)) |
| Итерация | for...of, встроенная |
Object.keys()/values/entries |
| Производительность | Лучше для частых добавлений/удалений | Лучше для статических данных |
| Прототип | Нет лишних ключей | Наследует от Object.prototype |
| JSON | Не сериализуется напрямую | Сериализуется |
// Когда использовать Map:
// 1. Ключи — не строки
const componentState = new Map();
componentState.set(document.body, { clicks: 0 });
// 2. Частые добавления/удаления
const cache = new Map();
// 3. Нужен точный size
console.log(cache.size);
// Когда использовать Object:
// 1. JSON-сериализация
const config = { host: 'localhost', port: 3000 };
JSON.stringify(config);
// 2. Деструктуризация
const { host, port } = config;
// 3. Статичные данные с известными ключами
Итерация
const map = new Map([
['JS', 'JavaScript'],
['TS', 'TypeScript'],
['PY', 'Python'],
]);
// for...of (ключ-значение)
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}
// Отдельно ключи, значения, пары
for (const key of map.keys()) { /* ... */ }
for (const value of map.values()) { /* ... */ }
for (const [k, v] of map.entries()) { /* ... */ }
// forEach
map.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// Конвертация
const obj = Object.fromEntries(map); // Map → Object
const arr = [...map]; // Map → Array of pairs
const newMap = new Map(Object.entries(obj)); // Object → Map
WeakMap
WeakMap хранит пары, где ключ — только объект, и не препятствует сборке мусора.
const weakMap = new WeakMap();
let element = document.querySelector('#app');
weakMap.set(element, { clicks: 0, lastVisit: new Date });
// Когда element удалён из DOM и нет других ссылок:
element = null;
// WeakMap автоматически удалит запись → нет утечки памяти
// Не поддерживает: size, итерацию, keys, values
// Используется для: приватные данные, метаданные объектов
// Паттерн: приватные данные через WeakMap
const privateData = new WeakMap();
class Person {
constructor(name, age) {
privateData.set(this, { name, age });
}
getName {
return privateData.get(this).name;
}
getAge {
return privateData.get(this).age;
}
}
const alice = new Person('Alice', 30);
alice.getName; // 'Alice'
// Нельзя получить данные снаружи без ссылки на weakMap
Практические примеры
Подсчёт частоты
function countFrequency(array) {
const freq = new Map();
for (const item of array) {
freq.set(item, (freq.get(item) || 0) + 1);
}
return freq;
}
const words = ['hello', 'world', 'hello', 'js', 'hello', 'world'];
console.log(countFrequency(words));
// Map { 'hello' => 3, 'world' => 2, 'js' => 1 }
Мемоизация
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
const expensiveFn = memoize((n) => {
console.log('Computing...');
return n * n;
});
expensiveFn(5); // Computing... 25
expensiveFn(5); // 25 (из кэша, без Computing)
Two Sum (LeetCode #1)
function twoSum(nums, target) {
const seen = new Map(); // значение → индекс
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (seen.has(complement)) {
return [seen.get(complement), i];
}
seen.set(nums[i], i);
}
return ; // не найдено
}
console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]
Сложность операций
| Операция | Сложность |
|---|---|
set(key, value) |
O(1) |
get(key) |
O(1) |
has(key) |
O(1) |
delete(key) |
O(1) |
size |
O(1) |
| Итерация | O(n) |
Частые ошибки
- Использовать Object как Map для произвольных ключей. Object автоматически конвертирует ключи в строки:
obj[1]иobj["1"]— одно и то же - Забывать, что Map не сериализуется в JSON. Нужна ручная конвертация через
Object.fromEntries - Сравнение ключей. Map использует SameValueZero (как ===, но NaN === NaN)
- Использовать массивы/объекты как ключи и ожидать совпадение по содержимому.
map.get([1,2])не найдётmap.set([1,2], 'x')— разные ссылки!
Практика
- Реализуйте
groupBy(array, keyFn)используя Map - Реализуйте LRU Cache (Least Recently Used) с ограниченным размером
- Решите задачу Two Sum (#1 LeetCode) используя Map
Связанные темы
- Set (только уникальные значения, без пар)
- Hash Table (Map реализован на хэш-таблице)
- Array (упорядоченная коллекция)
Ресурсы
- MDN — Map
- MDN — WeakMap
- learn.javascript.ru — Map и Set