Как делать11 октября 2026 г.6 мин чтения
JavaScript с нуляШаг 5 из 9

Массивы, объекты, Map и Set: структуры данных на практике

Коротко

Структура данных — способ хранить значения так, чтобы нужная операция была простой и быстрой. В JavaScript массив хранит упорядоченный список, объект — запись с известными полями, Map — словарь с ключами любого типа, Set — набор уникальных значений с быстрой проверкой «есть ли». Выбор начинается с вопроса, как вы будете обращаться к данным: по позиции, по имени поля, по произвольному ключу или по самому значению.

ОсновыУровень: НачальныйСтруктуры данных

После материала вы сможете

  • выбрать между массивом, объектом, Map и Set по тому, как данные будут читаться и меняться
  • преобразовать список через map, filter, find, reduce и toSorted, не трогая исходный массив
  • сгруппировать заказы через Object.groupBy или Map и посчитать итоги по группам
  • объяснить разницу между поверхностной копией и structuredClone

Проверено на

Node.js 24.18 LTS (V8 13.6)ECMAScript 2025

Сверено с документацией: 11 октября 2026 г.

Содержание
  1. 01Четыре структуры — четыре способа найти данные
  2. 02Массив: порядок, индексы и методы
  3. 03Объект: запись с полями
  4. 04Map против объекта: когда что
  5. 05Set: уникальность и проверка «есть ли»
  6. 06Копирование и мутация: spread и structuredClone
  7. 07Практика: группировка заказов по статусу

Четыре структуры — четыре способа найти данные

В реальной программе данные почти никогда не живут поодиночке. Заказы приходят списком, клиент описывается набором полей, статусам нужны подписи, а теги товара не должны повторяться. Для каждой из этих задач в JavaScript есть встроенная структура, и неудачный выбор обычно виден сразу: код обрастает ручными проверками, а на большом объёме начинает тормозить.

Array

Что хранит
Упорядоченный список, повторы допустимы
Как находят элемент
По индексу; по условию — перебором (find, includes)
Когда брать
Список для показа, сортировки, обхода по порядку

Object

Что хранит
Запись: свойства с именами-строками
Как находят элемент
По имени поля: order.status
Когда брать
Одна сущность с известными полями; данные для JSON

Map

Что хранит
Пары «ключ — значение», ключ любого типа
Как находят элемент
map.get(key)
Когда брать
Словарь, который растёт во время работы; ключи-объекты

Set

Что хранит
Уникальные значения
Как находят элемент
set.has(value)
Когда брать
Убрать повторы, проверить «есть ли в наборе»
Встроенные структуры JavaScript и их сильные стороны

Скорость тоже зависит от выбора. Спецификация требует, чтобы Map и Set в среднем находили элемент быстрее, чем за время, пропорциональное числу элементов: MDN поясняет, что внутри это может быть хеш-таблица или дерево поиска. Для Set MDN прямо пишет, что метод has в среднем быстрее, чем Array.prototype.includes на массиве той же длины: includes сравнивает искомое значение с элементами по очереди. Как оценивать такие различия, разбираем в уроке об алгоритмах и сложности.

Массив: порядок, индексы и методы

Массив — нумерованный список. Индексы начинаются с нуля, последний элемент стоит под номером length − 1. Массив в JavaScript можно увеличивать и уменьшать, а элементы могут быть разных типов. Метод at(-1) берёт элемент с конца без арифметики с длиной.

Основные методы принимают колбэк (callback) — функцию, которую метод вызывает для каждого элемента. Если стрелочные функции пока непривычны, вернитесь к уроку о функциях. Важно помнить, какие методы создают новый массив, а какие меняют исходный:

map

Что возвращает
Новый массив той же длины
Меняет исходный массив
Нет

filter

Что возвращает
Новый массив из подходящих элементов
Меняет исходный массив
Нет

find

Что возвращает
Первый подходящий элемент или undefined
Меняет исходный массив
Нет

reduce

Что возвращает
Одно значение: сумму, объект, Map
Меняет исходный массив
Нет

sort

Что возвращает
Тот же массив, уже отсортированный
Меняет исходный массив
Да

toSorted

Что возвращает
Отсортированную копию
Меняет исходный массив
Нет
arrays.mjs
const orders = [
  { id: 101, customer: "Айгерим", status: "paid", total: 12500 },
  { id: 102, customer: "Данияр", status: "new", total: 4800 },
  { id: 103, customer: "Айгерим", status: "shipped", total: 23000 },
  { id: 104, customer: "Мария", status: "paid", total: 7600 },
  { id: 105, customer: "Ержан", status: "cancelled", total: 3100 },
];

// Доступ по индексу: отсчёт с нуля, at(-1) — последний элемент
console.log(orders[0].id); // 101
console.log(orders.at(-1).id); // 105
console.log(orders.length); // 5

// map — новый массив той же длины
const ids = orders.map((order) => order.id);
console.log(ids); // [ 101, 102, 103, 104, 105 ]

// filter — новый массив из подходящих элементов
const paid = orders.filter((order) => order.status === "paid");
console.log(paid.length); // 2

// find — первый подходящий элемент или undefined
const large = orders.find((order) => order.total > 20000);
console.log(large?.customer); // Айгерим
console.log(orders.find((order) => order.total > 100000)); // undefined

// reduce — сворачивает массив в одно значение
const revenue = orders
  .filter((order) => order.status !== "cancelled")
  .reduce((sum, order) => sum + order.total, 0);
console.log(revenue); // 47900

// toSorted — отсортированная копия, исходный массив не меняется
const byTotal = orders.toSorted((a, b) => b.total - a.total);
console.log(byTotal.map((order) => order.id)); // [ 103, 101, 104, 102, 105 ]
console.log(orders[0].id); // 101

// Без функции сравнения сортировка сравнивает числа как строки
console.log([10, 9, 100, 1].toSorted()); // [ 1, 10, 100, 9 ]
console.log([10, 9, 100, 1].toSorted((a, b) => a - b)); // [ 1, 9, 10, 100 ]
Запуск: node arrays.mjs. Комментарии справа — реальный вывод Node.js 24.

Объект: запись с полями

Объект — набор свойств «имя — значение». Он подходит для описания одной сущности: у заказа есть номер, клиент, статус и сумма. В таком же виде данные обычно приходят из API в формате JSON, поэтому объект — самая привычная форма записи в веб-разработке.

Деструктуризация — синтаксис, который достаёт свойства в переменные одной строкой. При этом поле можно переименовать (total: amount) и задать значение по умолчанию на случай, если свойства нет.

objects.mjs
const order = { id: 101, customer: "Айгерим", status: "paid", total: 12500 };

// Чтение и запись свойств
console.log(order.customer); // Айгерим
order.status = "shipped";
console.log(order["status"]); // shipped

// Деструктуризация: достать поля в переменные, переименовать, задать значение по умолчанию
const { id, total: amount, comment = "без комментария" } = order;
console.log(id, amount, comment); // 101 12500 без комментария

// Ключи и пары «ключ — значение»
console.log(Object.keys(order)); // [ 'id', 'customer', 'status', 'total' ]
console.log(Object.entries(order)[0]); // [ 'id', 101 ]

// Объект как справочник с заранее известными ключами
const statusLabels = {
  new: "Новый",
  paid: "Оплачен",
  shipped: "Отправлен",
  cancelled: "Отменён",
};
console.log(statusLabels[order.status]); // Отправлен

// Нет ключа — undefined, без ошибки
console.log(statusLabels.returned); // undefined
console.log(statusLabels.returned ?? "Неизвестный статус"); // Неизвестный статус
Запуск: node objects.mjs.

Объект часто служит и справочником: статус → подпись для интерфейса. Это нормальное решение, пока ключи известны заранее, их немного и они не приходят от пользователя. Когда ключи появляются во время работы программы, лучше подходит Map.

Map против объекта: когда что

Долгое время объект был единственным словарём в JavaScript. Map появился позже, и MDN перечисляет различия, из-за которых он во многих случаях удобнее:

Ключи по умолчанию

Object
Есть унаследованные от прототипа, например constructor
Map
Только те, что добавили

Тип ключа

Object
Строка или Symbol
Map
Любой: объект, число, функция

Порядок ключей

Object
Определён, но по сложным правилам — MDN не советует на него полагаться
Map
Порядок добавления

Размер

Object
Object.keys(obj).length
Map
map.size

Перебор

Object
Через Object.keys или Object.entries
Map
Напрямую в for...of

Частые добавления и удаления

Object
Не оптимизирован под них
Map
Работает лучше

JSON

Object
JSON.stringify из коробки
Map
Нужно сначала преобразовать
Сравнение по таблице «Objects vs. Maps» из документации MDN
map.mjs
// Ловушка объекта-словаря: ключи из прототипа
const stock = { latte: 3 };
console.log("constructor" in stock); // true — унаследован от Object.prototype
console.log(Object.hasOwn(stock, "constructor")); // false

const stockMap = new Map([["latte", 3]]);
console.log(stockMap.has("constructor")); // false — в Map только то, что положили

// Ключом Map может быть объект; у обычного объекта ключ станет строкой
const aigerim = { id: 1 };
const daniyar = { id: 2 };

const lastVisit = new Map();
lastVisit.set(aigerim, "2026-10-01").set(daniyar, "2026-10-09");
console.log(lastVisit.get(aigerim)); // 2026-10-01
console.log(lastVisit.size); // 2

const asObject = {};
asObject[aigerim] = "2026-10-01";
asObject[daniyar] = "2026-10-09";
console.log(Object.keys(asObject)); // [ '[object Object]' ] — оба ключа превратились в одну строку

// Счётчик: частые добавления и обновления — сильная сторона Map
const visits = new Map();
for (const name of ["Айгерим", "Данияр", "Айгерим"]) {
  visits.set(name, (visits.get(name) ?? 0) + 1);
}
console.log(visits); // Map(2) { 'Айгерим' => 2, 'Данияр' => 1 }

// Map не превращается в JSON сам — сначала в объект
console.log(JSON.stringify(visits)); // {}
console.log(JSON.stringify(Object.fromEntries(visits))); // {"Айгерим":2,"Данияр":1}
Запуск: node map.mjs. Обратите внимание на строку с [object Object]: объект превратил оба ключа в одну строку.

Практическое правило: объект — для записи с фиксированными полями и для данных, которые уйдут в JSON; Map — для словаря, который наполняется во время работы программы, особенно если ключи не строки или их нужно часто добавлять и удалять.

Set: уникальность и проверка «есть ли»

Set — набор, где каждое значение встречается один раз. Повторное добавление ничего не меняет. Значения сравниваются почти как через ===: исключение — NaN, который в Set равен сам себе. Объекты сравниваются по ссылке, поэтому два одинаковых по содержимому объекта — два разных элемента.

set.mjs
const tags = ["кофе", "десерт", "кофе", "завтрак", "десерт"];

// Set хранит каждое значение один раз и помнит порядок добавления
const unique = new Set(tags);
console.log(unique.size); // 3
console.log([...unique]); // [ 'кофе', 'десерт', 'завтрак' ]

unique.add("кофе");
console.log(unique.size); // 3 — повтор не добавился
console.log(unique.has("завтрак")); // true

// Операции над множествами (Node.js 22+, актуальные браузеры)
const morning = new Set(["кофе", "сырники", "каша"]);
const evening = new Set(["кофе", "паста"]);
console.log(morning.intersection(evening)); // Set(1) { 'кофе' }
console.log(morning.difference(evening)); // Set(2) { 'сырники', 'каша' }
console.log(morning.union(evening).size); // 4

// Объекты сравниваются по ссылке, а не по содержимому
const pair = new Set([{ id: 1 }, { id: 1 }]);
console.log(pair.size); // 2

// Проверка «есть ли в списке»
const blockedIds = new Set([7, 19, 42]);
const incoming = [3, 19, 25, 42];
console.log(incoming.filter((id) => !blockedIds.has(id))); // [ 3, 25 ]
Запуск: node set.mjs. Методы intersection, union, difference есть в Node.js 22 и новее.

Методы intersection (пересечение), union (объединение) и difference (разность) делают то же, что операции над множествами в математике. Раньше их писали вручную через filter и has, теперь они встроены в язык.

Копирование и мутация: spread и structuredClone

Мутация — изменение уже существующего значения. Переменная с объектом или массивом хранит не сам объект, а ссылку на него, поэтому две переменные могут смотреть на одни и те же данные. Изменили через одну — увидели через другую.

MDN подчёркивает: все встроенные операции копирования — spread ..., Array.from, slice, concat — создают поверхностные копии. Новым становится только верхний уровень, а вложенные объекты и массивы остаются общими. Глубокую копию делает structuredClone: он копирует вложенные структуры и даже циклические ссылки, но выбрасывает ошибку DataCloneError, если встречает то, что скопировать нельзя, например функцию.

copy.mjs
const original = { id: 101, items: [{ sku: "latte", qty: 1 }] };

// Spread копирует только верхний уровень
const shallow = { ...original };
shallow.id = 999;
shallow.items[0].qty = 5;
console.log(original.id); // 101 — верхний уровень независим
console.log(original.items[0].qty); // 5 — вложенный массив общий

// structuredClone копирует вглубь
const deep = structuredClone(original);
deep.items[0].qty = 1;
console.log(original.items[0].qty); // 5
console.log(deep.items[0].qty); // 1

// Функции structuredClone не копирует
try {
  structuredClone({ format() {} });
} catch (error) {
  console.log(error.name); // DataCloneError
}

// sort меняет исходный массив, toSorted — нет
const totals = [300, 1000, 25];
const sorted = totals.toSorted((a, b) => a - b);
console.log(totals); // [ 300, 1000, 25 ]
console.log(sorted); // [ 25, 300, 1000 ]
totals.sort((a, b) => a - b);
console.log(totals); // [ 25, 300, 1000 ]
Запуск: node copy.mjs.
  • Плоский объект или массив без вложенности — хватает spread: { ...order }, [...items]
  • Вложенные данные, которые нужно менять независимо, — structuredClone
  • Нужен отсортированный или изменённый вариант без порчи оригинала — toSorted, toReversed, toSpliced, with вместо sort, reverse, splice

Практика: группировка заказов по статусу

Соберём всё вместе на типичной задаче из админки магазина: есть список заказов, нужно разложить их по статусам, посчитать количество и сумму в каждой группе, найти клиента с самой большой выручкой и узнать, сколько всего разных клиентов.

  1. Сгруппировать

    Object.groupBy раскладывает элементы по ключу, который вернул колбэк. Ключи — строки статусов, поэтому подходит объект. MDN отмечает, что результат создаётся без прототипа — лишних унаследованных ключей в нём нет.

  2. Посчитать итоги

    Object.entries превращает группы в пары, map строит строку отчёта, reduce суммирует заказы внутри группы.

  3. Собрать выручку по клиентам

    Клиенты появляются по ходу перебора, поэтому накапливаем суммы в Map и пропускаем отменённые заказы.

  4. Отсортировать

    Map превращаем в массив пар через spread и сортируем копию по сумме.

  5. Посчитать уникальных

    Set из имён клиентов сам уберёт повторы, ответ — в size.

group-orders.mjs
const orders = [
  { id: 101, customer: "Айгерим", status: "paid", total: 12500 },
  { id: 102, customer: "Данияр", status: "new", total: 4800 },
  { id: 103, customer: "Айгерим", status: "shipped", total: 23000 },
  { id: 104, customer: "Мария", status: "paid", total: 7600 },
  { id: 105, customer: "Ержан", status: "cancelled", total: 3100 },
  { id: 106, customer: "Данияр", status: "paid", total: 15400 },
  { id: 107, customer: "Мария", status: "new", total: 2900 },
];

// 1. Группировка по статусу: ключи — строки, поэтому подходит Object.groupBy
const byStatus = Object.groupBy(orders, (order) => order.status);
console.log(Object.keys(byStatus)); // [ 'paid', 'new', 'shipped', 'cancelled' ]
console.log(byStatus.paid.map((order) => order.id)); // [ 101, 104, 106 ]

// 2. Итоги по статусу: из каждой группы — количество и сумма
const summary = Object.entries(byStatus).map(([status, list]) => ({
  status,
  count: list.length,
  sum: list.reduce((total, order) => total + order.total, 0),
}));
console.log(summary[0]); // { status: 'paid', count: 3, sum: 35500 }

// 3. Выручка по клиентам без отменённых — Map: ключи добавляются на лету
const byCustomer = new Map();
for (const order of orders) {
  if (order.status === "cancelled") continue;
  byCustomer.set(order.customer, (byCustomer.get(order.customer) ?? 0) + order.total);
}
console.log(byCustomer.get("Айгерим")); // 35500

// 4. Топ клиентов по сумме: Map → массив пар → сортировка копии
const top = [...byCustomer].toSorted((a, b) => b[1] - a[1]);
console.log(top[0]); // [ 'Айгерим', 35500 ]

// 5. Уникальные клиенты — Set
const customers = new Set(orders.map((order) => order.customer));
console.log(customers.size); // 4
Запуск: node group-orders.mjs. Если вывод совпал с комментариями — решение работает.

Если ключом группы должна быть не строка — число, true/false или объект, — используйте Map.groupBy: он работает так же, но возвращает Map, и ключи не превращаются в строки.

Как проверено2 · пройденосверено 11 октября 2026 г.
  • запущено, работает

    arrays.mjs, objects.mjs, map.mjs, set.mjs, copy.mjs, group-orders.mjs (RU и EN)

    node 24.18.0 (V8 13.6) во временной папке scratchpad/kb/verify/struktury-dannyh/{ru,en}; каждый файл запущен с --import check-preload.mjs, который перехватывает console.log и сверяет вывод с комментарием в той же строке: 52 из 52 совпали в каждой локали

  • запущено, работает

    решения упражнений ex1–ex3 (RU и EN)

    тот же прогон с массивом orders из практики: 6 из 6 комментариев совпали

«Запущено» — код выполнен во временном проекте или контейнере, вывод в статье взят из прогона. «Частично» — проверено с заглушками вместо внешних сервисов. «Не запускалось» — нужны устройство, аккаунт или ключ; шаги сверены с официальной документацией. Файлы проверочного проекта и логи запусков сохранены в архиве InterLAB.

Расскажите о задаче — мы изучим запрос и свяжемся с вами

Есть задача?
Обсудим и оценим — бесплатно.

Расскажите о продукте или проблеме. Предложим архитектуру, срок и стоимость первой версии после изучения задачи.

  • Оценка объёма и рисков до старта
  • NDA — по вашему или нашему шаблону
  • Старт работ в течение недели

Не присылайте через форму пароли, ключи доступа и исходный код — безопасную передачу доступа обсудим отдельно.

Нажимая кнопку, вы соглашаетесь с обработкой персональных данных.