Перейти к содержимому
Теория · Map, Set и JSON · урок 1 из 9

Коллекция Map

Чему вы научитесь

Чему вы научитесь:

  • хранить пары «ключ — значение» в Map и считать в нём слова;
  • собирать значения без повторов в Set;
  • превращать объект в строку JSON и обратно и знать, что при этом теряется;
  • объяснять на собеседовании, чем Map отличается от обычного объекта.
Вешалка Map с двумя крючками и двумя пальто, на номерках 1 и '1' — два разных ключа

Представьте гардероб в театре. Вы отдаёте пальто, а гардеробщик выдаёт номерок. Номерок — это ключ, пальто — значение. После спектакля вы показываете номерок и получаете своё пальто обратно.

Объект в JavaScript тоже хранит пары «ключ — значение», но у него есть особенность: число в роли ключа он превращает в строку. Это как гардеробщик, который читает любой номерок как текст. Для него номерок 1 и номерок "1" — один и тот же крючок. Давайте посмотрим:

JavaScript
const names = {};
names[1] = "one";
names["1"] = "uno";
console.log(Object.keys(names).length); // 1

const map = new Map();
map.set(1, "one");
map.set("1", "uno");
console.log(map.size); // 2
console.log(map.get(1), map.get("1")); // one uno

Разберём по шагам:

  1. names[1] = "one" записывает в объект ключ "1": число стало строкой.
  2. names["1"] = "uno" пишет под тот же ключ и заменяет "one".
  3. Поэтому у объекта один ключ, и программа печатает 1.
  4. Во второй половине Map хранит 1 и "1" как два разных ключа: size равен 2.

Что такое Map

Map — встроенная коллекция пар «ключ — значение». Коллекцией называют значение, которое хранит внутри много других значений. Ключ в Map остаётся таким, каким его передали: числом, строкой, true или объектом.

Пустую коллекцию создаёт запись new Map(), и слово new в ней обязательно. new создаёт новое значение: здесь — новую пустую коллекцию. Без него вызов Map() бросает TypeError.

Методы Map

С Map работают через методы, а не через запись map.key или map[key], как у объекта:

  • map.set(key, value) записывает значение под ключом, а для существующего ключа заменяет старое значение;
  • map.get(key) возвращает значение или undefined, если такого ключа нет;
  • map.has(key) возвращает true или false;
  • map.delete(key) удаляет пару;
  • map.size хранит число пар — это свойство, поэтому скобок после него нет.

Свойства length у Map нет, и map.length даёт undefined.

JavaScript
const stock = new Map();
stock.set("apple", 3);
stock.set("pear", 5);
stock.set("apple", 4);
stock.delete("pear");

console.log(stock.get("apple"), stock.get("plum")); // 4 undefined
console.log(stock.has("pear"), stock.size); // false 1

const ann = { name: "Ann" };
const visits = new Map();
visits.set(ann, 2);
console.log(visits.get(ann), visits.get({ name: "Ann" })); // 2 undefined

Давайте разберём склад stock:

  1. Первые два set кладут яблоки и груши.
  2. Третий set с ключом "apple" не добавляет пару, а заменяет 3 на 4.
  3. delete("pear") убирает груши.
  4. Поэтому get("apple") даёт 4, а get("plum") — undefined: слив на складе нет.
  5. has("pear") даёт false, а size равен 1.

Ключ-объект

Ключ-объект сравнивается по ссылке. get находит значение только по тому же самому объекту. Другой объект с такими же свойствами — уже другой ключ, поэтому visits.get({ name: "Ann" }) даёт undefined.

Здесь аналогия с гардеробом перестаёт работать. Настоящий гардеробщик смотрит на число на номерке, и копия с тем же числом тоже подойдёт. Map с ключом-объектом узнаёт только тот самый номерок, а не его копию.

Подсчёт через Map

Частая задача — посчитать, сколько раз встретилось каждое слово. Для этого get сочетают с оператором ??: counts.set(word, (counts.get(word) ?? 0) + 1). Если ключа ещё нет, get вернёт undefined, ?? заменит его на 0, и под ключом запишется 1.

Нажимайте «Следующий шаг» и следите за old: для нового слова он равен 0, а для знакомого — прежнему счёту.

ВживуюПодсчёт слов в Mapшаг 1 из 10 · рамок: 1

Коллекция counts создана, и пар в ней пока нет.

get берёт старый счёт, set записывает новый

Под каждым словом лежит его счёт, а size равен числу разных слов.

Обход Map

Map помнит порядок, в котором добавляли ключи. Цикл for…of проходит пары именно в этом порядке. Каждая пара — массив [key, value], и его удобно разобрать деструктуризацией:

JavaScript
const scores = new Map();
scores.set("Bob", 7);
scores.set("Ann", 9);
scores.set(10, 1);

for (const [key, value] of scores) {
    console.log(key, value);
}
// Bob 7
// Ann 9
// 10 1

Ключ 10 остался последним, хотя в объекте целочисленный ключ вышел бы вперёд. Запись [...scores] с синтаксисом расширения превращает Map в массив таких пар.

Map или объект

Этот вопрос часто задают на собеседовании: «Чем Map отличается от объекта?» Ответ из трёх частей:

  • ключом Map может быть любое значение, а объект превращает число в строку;
  • Map помнит порядок добавления ключей и хранит число пар в size;
  • с Map работают через get, set, has и delete.

Объект удобен для данных с заранее известными свойствами, например user.name и user.age. Map удобен для словаря, ключи которого появляются во время работы программы: слова из текста, номера заказов, объекты.

Частая ошибка

Значение записали в Map квадратными скобками, как в объект:

JavaScript
const stock = new Map();
stock["apple"] = 3; // ✗

console.log(stock.get("apple"), stock.size); // undefined 0

Ошибки нет, но пара в коллекцию не попала. Скобки создали у объекта stock обычное свойство, а get и size его не видят. Записывайте пары только через set: stock.set("apple", 3).

Главное

  • Map хранит пары «ключ — значение», и ключ остаётся таким, каким его передали: 1 и "1" — разные ключи.
  • Пары пишут через set, читают через get, а их число хранит size.
  • Ключ-объект ищется по ссылке: подойдёт только тот же самый объект.
  • Для подсчёта пишут counts.set(word, (counts.get(word) ?? 0) + 1).

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом