Структура данных — способ хранить значения так, чтобы нужная операция была простой и быстрой. В 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 г.
Содержание
Четыре структуры — четыре способа найти данные
В реальной программе данные почти никогда не живут поодиночке. Заказы приходят списком, клиент описывается набором полей, статусам нужны подписи, а теги товара не должны повторяться. Для каждой из этих задач в JavaScript есть встроенная структура, и неудачный выбор обычно виден сразу: код обрастает ручными проверками, а на большом объёме начинает тормозить.
Array
- Что хранит
- Упорядоченный список, повторы допустимы
- Как находят элемент
- По индексу; по условию — перебором (
find,includes) - Когда брать
- Список для показа, сортировки, обхода по порядку
Object
- Что хранит
- Запись: свойства с именами-строками
- Как находят элемент
- По имени поля:
order.status - Когда брать
- Одна сущность с известными полями; данные для JSON
Map
- Что хранит
- Пары «ключ — значение», ключ любого типа
- Как находят элемент
map.get(key)- Когда брать
- Словарь, который растёт во время работы; ключи-объекты
Set
- Что хранит
- Уникальные значения
- Как находят элемент
set.has(value)- Когда брать
- Убрать повторы, проверить «есть ли в наборе»
Скорость тоже зависит от выбора. Спецификация требует, чтобы Map и Set в среднем находили элемент быстрее, чем за время, пропорциональное числу элементов: MDN поясняет, что внутри это может быть хеш-таблица или дерево поиска. Для Set MDN прямо пишет, что метод has в среднем быстрее, чем Array.prototype.includes на массиве той же длины: includes сравнивает искомое значение с элементами по очереди. Как оценивать такие различия, разбираем в уроке об алгоритмах и сложности.
Массив: порядок, индексы и методы
Массив — нумерованный список. Индексы начинаются с нуля, последний элемент стоит под номером length − 1. Массив в JavaScript можно увеличивать и уменьшать, а элементы могут быть разных типов. Метод at(-1) берёт элемент с конца без арифметики с длиной.
Основные методы принимают колбэк (callback) — функцию, которую метод вызывает для каждого элемента. Если стрелочные функции пока непривычны, вернитесь к уроку о функциях. Важно помнить, какие методы создают новый массив, а какие меняют исходный:
map
- Что возвращает
- Новый массив той же длины
- Меняет исходный массив
- Нет
filter
- Что возвращает
- Новый массив из подходящих элементов
- Меняет исходный массив
- Нет
find
- Что возвращает
- Первый подходящий элемент или
undefined - Меняет исходный массив
- Нет
reduce
- Что возвращает
- Одно значение: сумму, объект, Map
- Меняет исходный массив
- Нет
sort
- Что возвращает
- Тот же массив, уже отсортированный
- Меняет исходный массив
- Да
toSorted
- Что возвращает
- Отсортированную копию
- Меняет исходный массив
- Нет
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) и задать значение по умолчанию на случай, если свойства нет.
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
- Нужно сначала преобразовать
// Ловушка объекта-словаря: ключи из прототипа
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 равен сам себе. Объекты сравниваются по ссылке, поэтому два одинаковых по содержимому объекта — два разных элемента.
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, если встречает то, что скопировать нельзя, например функцию.
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
Практика: группировка заказов по статусу
Соберём всё вместе на типичной задаче из админки магазина: есть список заказов, нужно разложить их по статусам, посчитать количество и сумму в каждой группе, найти клиента с самой большой выручкой и узнать, сколько всего разных клиентов.
Сгруппировать
Object.groupByраскладывает элементы по ключу, который вернул колбэк. Ключи — строки статусов, поэтому подходит объект. MDN отмечает, что результат создаётся без прототипа — лишних унаследованных ключей в нём нет.Посчитать итоги
Object.entriesпревращает группы в пары,mapстроит строку отчёта,reduceсуммирует заказы внутри группы.Собрать выручку по клиентам
Клиенты появляются по ходу перебора, поэтому накапливаем суммы в Map и пропускаем отменённые заказы.
Отсортировать
Map превращаем в массив пар через spread и сортируем копию по сумме.
Посчитать уникальных
Set из имён клиентов сам уберёт повторы, ответ — в
size.
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); // 4node 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.