JavaScript — массивы и объекты

Массивы хранят упорядоченные коллекции, а объекты — данные в виде пар «ключ — значение».

const skills = ["HTML", "CSS", "JavaScript"];
const user = { name: "Анна", age: 28 };

Индексы массива начинаются с 0. Массив является объектом, поэтому для проверки используют не typeof, а Array.isArray():

Array.isArray(skills); // true
typeof skills;         // "object"

Массивы

const colors = ["red", "green", "blue"];

colors[0];            // "red"
colors.length;        // 3
colors.at(-1);        // "blue"
colors[1] = "yellow";

Методы push, pop, shift и unshift изменяют исходный массив:

const items = ["b", "c"];
items.push("d");      // добавить в конец
items.pop();          // удалить с конца
items.unshift("a");  // добавить в начало
items.shift();        // удалить из начала

Методы массивов

Большинство перебирающих методов принимает callback:

array.method((element, index, sourceArray) => {
  // обработка элемента
});
Метод Назначение Результат Мутирует массив
map() Преобразовать каждый элемент Новый массив Нет
filter() Выбрать подходящие элементы Новый массив Нет
reduce() Свести коллекцию к одному результату Любое значение Обычно нет
forEach() Выполнить действие для каждого элемента undefined Сам по себе нет
find() Найти первый подходящий элемент Элемент или undefined Нет
some() Проверить «хотя бы один» boolean Нет
every() Проверить «все» boolean Нет
sort() Отсортировать массив Тот же массив Да

map()

Создаёт массив той же длины, преобразуя каждый элемент:

const numbers = [1, 2, 3];
const squares = numbers.map((number) => number ** 2);
// [1, 4, 9]

Получение свойств объектов:

const users = [
  { id: 1, name: "Анна" },
  { id: 2, name: "Борис" },
];

const names = users.map((user) => user.name);
// ["Анна", "Борис"]

При возврате объекта в краткой стрелочной функции нужны круглые скобки:

const cards = names.map((name) => ({ name, active: true }));

filter()

Возвращает элементы, для которых callback вернул истинное значение:

const numbers = [1, 2, 3, 4, 5, 6];
const even = numbers.filter((number) => number % 2 === 0);
// [2, 4, 6]
const available = products.filter(
  (product) => product.inStock && product.price < 10000
);

Если совпадений нет, возвращается [].

reduce()

Последовательно накапливает результат:

const result = array.reduce((accumulator, element) => {
  return newAccumulator;
}, initialValue);

Сумма:

const total = [10, 20, 30].reduce(
  (sum, number) => sum + number,
  0
); // 60

Стоимость корзины:

const cart = [
  { price: 1200, quantity: 2 },
  { price: 350, quantity: 3 },
];

const total = cart.reduce(
  (sum, item) => sum + item.price * item.quantity,
  0
); // 3450

Начальное значение лучше указывать явно. Вызов reduce() без него для пустого массива создаст TypeError.

forEach()

Выполняет действие для каждого элемента и возвращает undefined:

users.forEach((user, index) => {
  console.log(`${index + 1}. ${user.name}`);
});

Подходит для вывода, изменения DOM и других побочных эффектов. Если нужен новый массив, следует использовать map(). Обычные break и continue внутри forEach() недоступны.

find()

Возвращает первый подходящий элемент:

const user = users.find((item) => item.id === 2);
// { id: 2, name: "Борис" }

Если совпадения нет, возвращается undefined. findIndex() аналогично возвращает индекс или -1.

const index = users.findIndex((item) => item.id === 2); // 1

some() и every()

const ages = [17, 22, 31];

ages.some((age) => age >= 18);  // true: хотя бы один
ages.every((age) => age >= 18); // false: не все

Методы завершают перебор, когда результат уже известен.

sort()

sort() изменяет исходный массив. По умолчанию элементы сравниваются как строки:

[2, 10, 100].sort(); // [10, 100, 2]

Числа по возрастанию и убыванию:

numbers.sort((a, b) => a - b);
numbers.sort((a, b) => b - a);

Объекты по цене:

products.sort((a, b) => a.price - b.price);

Строки с учётом языка:

users.sort((a, b) => a.name.localeCompare(b.name, "ru"));

Сортировка без изменения оригинала:

const sortedA = [...numbers].sort((a, b) => a - b);
const sortedB = numbers.toSorted((a, b) => a - b);

toSorted() возвращает новый массив.


Деструктуризация массивов

const coordinates = [55.75, 37.62];
const [latitude, longitude] = coordinates;

Пропуск элементов и значения по умолчанию:

const [first, , third] = [10, 20, 30];
const [theme = "light", language = "ru"] = ["dark"];

Остальные элементы:

const [head, ...tail] = [10, 20, 30, 40];
// head: 10, tail: [20, 30, 40]

Обмен значений:

let a = 1;
let b = 2;
[a, b] = [b, a];

Rest-элемент должен находиться последним.


Объекты

Создание и свойства

const user = {
  name: "Анна",
  age: 28,
  active: true,
};

Доступ к свойствам:

user.name;       // точечная запись
user["age"];    // квадратные скобки

const field = "active";
user[field];     // имя свойства из переменной

Вычисляемое имя свойства:

const field = "email";
const user = {
  name: "Анна",
  [field]: "anna@example.com",
};

Добавление, изменение и удаление:

user.city = "Казань";
user.age = 29;
delete user.active;

Проверка наличия:

"name" in user;                 // включая унаследованные
Object.hasOwn(user, "name");    // только собственное свойство

user.value === undefined не отличает отсутствующее свойство от существующего свойства со значением undefined.

Ключи, значения и пары

Object.keys(user);    // ["name", "age", ...]
Object.values(user);  // ["Анна", 29, ...]
Object.entries(user); // [["name", "Анна"], ...]
for (const [key, value] of Object.entries(user)) {
  console.log(`${key}: ${value}`);
}

Краткая запись свойств

const name = "Анна";
const age = 28;

const user = { name, age };

Это эквивалентно { name: name, age: age }.


Методы объектов и this

Функция в свойстве объекта называется методом:

const user = {
  name: "Анна",

  greet() {
    return `Привет, ${this.name}!`;
  },
};

user.greet(); // "Привет, Анна!"

this определяется способом вызова функции. При вызове user.greet() он указывает на объект слева от точки.

function greet() {
  return `Привет, ${this.name}!`;
}

const anna = { name: "Анна", greet };
const boris = { name: "Борис", greet };

anna.greet();
boris.greet();

Если отделить метод от объекта, контекст теряется:

const greetSeparately = user.greet;
greetSeparately();

В строгом режиме this при таком вызове равен undefined. Контекст можно закрепить:

const boundGreet = user.greet.bind(user);

Стрелочная функция не создаёт собственный this, поэтому обычно не подходит для метода, которому нужен объект:

// Нежелательно
const user = {
  name: "Анна",
  greet: () => this.name,
};

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

const team = {
  name: "Frontend",
  members: ["Анна", "Борис"],

  print() {
    this.members.forEach((member) => {
      console.log(`${member}: ${this.name}`);
    });
  },
};

Деструктуризация объектов

Свойства извлекаются по именам, поэтому порядок не важен:

const user = { name: "Анна", age: 28, city: "Казань" };
const { name, age } = user;

Переименование и значение по умолчанию:

const {
  name: userName,
  role: userRole = "user",
} = user;

Остальные свойства:

const { age, ...publicData } = user;

Деструктуризация параметров:

function printUser({ name, age = 0 } = {}) {
  console.log(`${name}, возраст: ${age}`);
}

Вложенная деструктуризация:

const profile = {
  address: { city: "Казань" },
};

const { address: { city } = {} } = profile;

Если вложенное свойство может отсутсвовать, используйте безопасное значение по умолчанию.


Spread и rest

Оба оператора записываются как ..., но выполняют разные задачи.

Spread: раскрытие значений

Копирование и объединение массивов:

const first = [1, 2];
const second = [3, 4];
const combined = [...first, ...second];

Передача аргументов:

const maximum = Math.max(...combined); // 4

Копирование и обновление объекта:

const user = { name: "Анна", age: 28 };
const updatedUser = { ...user, age: 29 };

Свойство, записанное позже, перезаписывает предыдущее.

Rest: сбор оставшихся значений

function sum(...numbers) {
  return numbers.reduce((total, number) => total + number, 0);
}

sum(10, 20, 30); // 60
const [first, ...otherItems] = [1, 2, 3];
const { password, ...safeUser } = user;

Rest должен находиться последним в списке параметров или шаблоне массива.


JSON

JSON — текстовый формат обмена данными.

{
  "name": "Анна",
  "age": 28,
  "active": true,
  "skills": ["HTML", "CSS"]
}

В JSON ключи и строки заключаются в двойные кавычки. Комментарии, завершающие запятые, функции, undefined, Symbol и BigInt не входят в обычный JSON-синтаксис.

JSON.stringify()

Преобразует JavaScript-значение в строку JSON:

const json = JSON.stringify(user);
const formatted = JSON.stringify(user, null, 2);

Особенности:

JSON.parse()

Преобразует JSON-строку в JavaScript-значение:

const text = '{"name":"Анна","age":28}';
const data = JSON.parse(text);

Некорректный JSON вызывает SyntaxError:

function parseSafely(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    console.error("Некорректный JSON:", error.message);
    return null;
  }
}

JSON не хранит тип Date: после parse() дата остаётся строкой и при необходимости преобразуется через new Date(value).


Map

Map хранит пары «ключ — значение». Ключом может быть значение любого типа.

const roles = new Map([
  ["admin", "Администратор"],
  ["editor", "Редактор"],
]);

roles.set("user", "Пользователь");
roles.get("admin");    // "Администратор"
roles.has("editor");   // true
roles.delete("editor");
roles.size;
roles.clear();

Перебор:

for (const [key, value] of roles) {
  console.log(key, value);
}

Объект может быть ключом, но сравнивается по ссылке:

const user = { id: 1 };
const cache = new Map();
cache.set(user, "данные");

cache.get(user);      // "данные"
cache.get({ id: 1 }); // undefined

Преобразование:

const map = new Map(Object.entries({ theme: "dark" }));
const object = Object.fromEntries(map);

Обычный объект удобен для сущности с известными полями. Map — для динамической коллекции, частого добавления и удаления или ключей произвольного типа.


Set

Set хранит уникальные значения:

const tags = new Set(["js", "web", "js"]);

tags.size;          // 2
tags.add("frontend");
tags.has("js");     // true
tags.delete("web");
tags.clear();

Удаление дубликатов:

const numbers = [1, 2, 2, 3, 3];
const unique = [...new Set(numbers)];
// [1, 2, 3]

Перебор:

for (const value of tags) {
  console.log(value);
}

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


Ссылки и иммутабельность

Объекты и массивы — ссылочные типы:

const first = { name: "Анна" };
const second = first;

second.name = "Вера";
console.log(first.name); // "Вера"

=== сравнивает ссылки:

{ id: 1 } === { id: 1 }; // false

Иммутабельное обновление создаёт новую структуру вместо изменения исходной:

const updatedUser = { ...user, age: 29 };
const withNewItem = [...items, newItem];
const withoutItem = items.filter((item) => item.id !== deletedId);

Замена объекта в массиве:

const updatedUsers = users.map((user) =>
  user.id === 2
    ? { ...user, active: true }
    : user
);

Иммутабельность делает изменения предсказуемее и упрощает сравнение старого и нового состояния.


Shallow copy и deep copy

Поверхностная копия

Spread, Object.assign(), slice() и Array.from() создают поверхностную копию. Внешняя структура новая, но вложенные объекты остаются общими:

const original = {
  name: "Анна",
  address: { city: "Казань" },
};

const copy = { ...original };

copy === original;                 // false
copy.address === original.address; // true

Изменение copy.address.city изменит и original.address.city.

Для иммутабельного изменения копируют каждый изменяемый уровень:

const updated = {
  ...original,
  address: {
    ...original.address,
    city: "Самара",
  },
};

Глубокая копия

structuredClone() создаёт независимые вложенные структуры:

const copy = structuredClone(original);

copy.address === original.address; // false

Он поддерживает массивы, обычные объекты, Date, Map, Set, типизированные массивы и циклические ссылки. Функции клонировать нельзя.

Иногда используют:

const copy = JSON.parse(JSON.stringify(original));

Это не универсальное глубокое копирование: теряются undefined, функции и специальные типы; Date становится строкой; Map и Set не сохраняются как коллекции; циклические ссылки вызывают ошибку.

const не делает объект неизменяемым:

const user = { name: "Анна" };
user.name = "Вера"; // допустимо
// user = {};       // ошибка

Object.freeze() запрещает изменение только на верхнем уровне:

const config = Object.freeze({
  database: { host: "localhost" },
});

config.database.host = "example.com"; // вложенный объект не заморожен

Частые ошибки

Использование forEach() вместо map()

const doubled = [1, 2, 3].forEach((n) => n * 2);
// undefined

Правильно:

const doubled = [1, 2, 3].map((n) => n * 2);

Отсутствие return в map()

const result = [1, 2].map((n) => {
  n * 2;
}); // [undefined, undefined]
const result = [1, 2].map((n) => n * 2);

Случайная мутация через sort()

const sorted = source.toSorted((a, b) => a - b);

Ожидание глубокого копирования от spread

const copy = { ...original }; // только shallow copy

Стрелочный метод с this

const user = {
  name: "Анна",
  getName() {
    return this.name;
  },
};

Если нужен this объекта, используйте обычный метод.


Общий пример

const orders = [
  {
    id: 101,
    customer: { id: 1, name: "Анна" },
    status: "paid",
    items: [
      { title: "Книга", price: 1200, quantity: 2 },
      { title: "Блокнот", price: 350, quantity: 1 },
    ],
  },
  {
    id: 102,
    customer: { id: 2, name: "Борис" },
    status: "new",
    items: [{ title: "Книга", price: 1200, quantity: 1 }],
  },
];

const paidOrders = orders.filter(({ status }) => status === "paid");

const ordersWithTotals = paidOrders.map((order) => ({
  ...order,
  total: order.items.reduce(
    (sum, { price, quantity }) => sum + price * quantity,
    0
  ),
}));

const revenue = ordersWithTotals.reduce(
  (sum, order) => sum + order.total,
  0
);

const productTitles = [
  ...new Set(
    orders.flatMap((order) =>
      order.items.map(({ title }) => title)
    )
  ),
];

const ordersById = new Map(
  ordersWithTotals.map((order) => [order.id, order])
);

const updatedOrders = orders.map((order) =>
  order.id === 102 ? { ...order, status: "paid" } : order
);

const report = {
  revenue,
  productTitles,
  hasNewOrders: orders.some((order) => order.status === "new"),
  allHaveItems: orders.every((order) => order.items.length > 0),
};

console.log(JSON.stringify(report, null, 2));
console.log(ordersById.get(101));
console.log(updatedOrders);

Краткие рекомендации