⚡ JavaScript

Методы массивов и коллекций в JavaScript (Node.js): подробное руководство

Что это такое и зачем? (аналогия из реальной жизни)

Представь, что ты — библиотекарь. У тебя есть огромный стеллаж с книгами. Каждая книга — это отдельный элемент данных. Раньше, чтобы найти книгу по автору, тебе приходилось вручную перебирать каждую книгу, записывать её название на листочек, потом искать следующую... Это долго и утомительно.

А теперь представь, что у тебя появился волшебный помощник-робот. Ты говоришь ему: «Найди все книги Толстого и поставь их на верхнюю полку». Робот мгновенно сканирует стеллаж, отбирает нужные книги и выполняет задание. Методы массивов и коллекций в JavaScript — это и есть такие волшебные команды для твоего кода.

Массив — это просто упорядоченный список чего угодно: чисел, строк, других массивов или объектов. А методы — это встроенные функции, которые умеют с этим списком работать: добавлять, удалять, искать, сортировать, преобразовывать элементы.

💡 Важно: Методы массивов не меняют исходный массив (если не сказано обратное). Они возвращают новый результат. Это как ксерокс: ты делаешь копию списка и работаешь с ней, а оригинал остаётся нетронутым.

Теория: как это работает изнутри

Давай заглянем «под капот» JavaScript. Массив — это объект специального типа. У него есть числовые индексы (0, 1, 2...) и свойство length (длина). Когда ты вызываешь метод, движок JavaScript (например, V8 в Node.js) проходит по элементам массива в цикле и применяет к каждому из них заданную функцию.

Рассмотрим самый популярный метод — forEach. Он просто «пробегает» по каждому элементу и выполняет для него действие. Внутри него происходит примерно такой цикл:

// Упрощённая внутренняя реализация forEach
function myForEach(array, callback) {
  for (let i = 0; i < array.length; i++) {
    // Вызываем callback-функцию для каждого элемента
    callback(array[i], i, array);
  }
}

// Использование:
const numbers = [10, 20, 30];
myForEach(numbers, (item, index) => {
  console.log(`Элемент ${item} на позиции ${index}`);
});
// Вывод:
// Элемент 10 на позиции 0
// Элемент 20 на позиции 1
// Элемент 30 на позиции 2

Ключевое понятие: callback-функция. Это функция, которую ты передаёшь методу. Метод сам решает, когда её вызвать и с какими аргументами. Обычно callback получает три аргумента:

  • currentValue — текущий элемент массива
  • index — его индекс (номер позиции)
  • array — сам массив (нужен редко)

Методы делятся на несколько групп:

  • Мутирующие — изменяют исходный массив (например, push, pop, splice)
  • Немутирующие — возвращают новый массив (например, map, filter, slice)
  • Итерационные — проходят по массиву, но ничего не возвращают (forEach)
  • Поисковые — находят элемент или его индекс (find, indexOf)

Синтаксис в JavaScript (Node.js): все формы и варианты

Рассмотрим основные методы с примерами синтаксиса. Все примеры работают в Node.js версии 18+.

1. Добавление и удаление элементов

Метод Что делает Мутирует? Возвращает
push(el1, el2, ...) Добавляет элементы в конец ✅ Да Новую длину массива
pop() Удаляет последний элемент ✅ Да Удалённый элемент
unshift(el1, ...) Добавляет элементы в начало ✅ Да Новую длину
shift() Удаляет первый элемент ✅ Да Удалённый элемент
splice(start, deleteCount, item1, ...) Удаляет, заменяет или добавляет элементы ✅ Да Массив удалённых элементов
// Примеры с комментариями
const fruits = ['яблоко', 'банан'];

// push — добавляем в конец
const newLength = fruits.push('апельсин', 'киви');
console.log(fruits); // ['яблоко', 'банан', 'апельсин', 'киви']
console.log(newLength); // 4 — метод вернул новую длину

// pop — забираем последний
const lastFruit = fruits.pop();
console.log(lastFruit); // 'киви'
console.log(fruits); // ['яблоко', 'банан', 'апельсин']

// unshift — добавляем в начало
fruits.unshift('груша');
console.log(fruits); // ['груша', 'яблоко', 'банан', 'апельсин']

// shift — удаляем первый
const firstFruit = fruits.shift();
console.log(firstFruit); // 'груша'

// splice — удаляем 1 элемент начиная с индекса 1
const removed = fruits.splice(1, 1);
console.log(removed); // ['банан']
console.log(fruits); // ['груша', 'апельсин']

2. Преобразование и фильтрация

Метод Что делает Возвращает
map(callback) Создаёт новый массив, преобразуя каждый элемент Новый массив той же длины
filter(callback) Создаёт новый массив из элементов, прошедших проверку Новый массив (может быть короче)
reduce(callback, initialValue) Сводит массив к одному значению Одно значение (число, строка, объект)
flat(depth) «Разглаживает» вложенные массивы Новый одномерный массив
const numbers = [1, 2, 3, 4, 5];

// map — умножаем каждый элемент на 10
const multiplied = numbers.map(num => num * 10);
console.log(multiplied); // [10, 20, 30, 40, 50]
// Исходный массив не изменился:
console.log(numbers); // [1, 2, 3, 4, 5]

// filter — оставляем только чётные числа
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4]

// reduce — считаем сумму всех чисел
const sum = numbers.reduce((accumulator, current) => {
  return accumulator + current;
}, 0); // 0 — начальное значение
console.log(sum); // 15

// flat — превращаем [[1,2], [3,4]] в [1,2,3,4]
const nested = [[1, 2], [3, [4, 5]]];
const flatOnce = nested.flat(1);
console.log(flatOnce); // [1, 2, 3, [4, 5]] — только один уровень
const fullyFlat = nested.flat(2);
console.log(fullyFlat); // [1, 2, 3, 4, 5]

3. Поиск и проверка

Метод Что делает Возвращает
find(callback) Находит первый элемент, удовлетворяющий условию Элемент или undefined
findIndex(callback) Находит индекс первого подходящего элемента Индекс или -1
includes(value) Проверяет, есть ли значение в массиве true или false
some(callback) Проверяет, удовлетворяет ли хотя бы один элемент условию true или false
every(callback) Проверяет, удовлетворяют ли все элементы условию true или false
const users = [
  { name: 'Анна', age: 25 },
  { name: 'Иван', age: 17 },
  { name: 'Мария', age: 30 }
];

// find — ищем первого совершеннолетнего
const adult = users.find(user => user.age >= 18);
console.log(adult); // { name: 'Анна', age: 25 }

// findIndex — ищем индекс несовершеннолетнего
const minorIndex = users.findIndex(user => user.age < 18);
console.log(minorIndex); // 1 (Иван)

// includes — проверяем наличие строки
const names = ['Анна', 'Иван', 'Мария'];
console.log(names.includes('Иван')); // true
console.log(names.includes('Петр')); // false

// some — есть ли кто-то младше 18?
const hasMinor = users.some(user => user.age < 18);
console.log(hasMinor); // true

// every — все ли старше 18?
const allAdults = users.every(user => user.age >= 18);
console.log(allAdults); // false (Иван 17 лет)

4. Сортировка и реверс

const letters = ['c', 'a', 'b', 'd'];

// sort() — сортирует как строки по умолчанию
letters.sort();
console.log(letters); // ['a', 'b', 'c', 'd']
// ВНИМАНИЕ: sort() мутирует исходный массив!

// Для чисел нужна функция сравнения
const nums = [10, 5, 80, 2];
nums.sort((a, b) => a - b); // по возрастанию
console.log(nums); // [2, 5, 10, 80]

nums.sort((a, b) => b - a); // по убыванию
console.log(nums); // [80, 10, 5, 2]

// reverse() — переворачивает массив (тоже мутирует)
const arr = [1, 2, 3];
arr.reverse();
console.log(arr); // [3, 2, 1]
⚠️ Важно: sort() и reverse() изменяют исходный массив. Если нужно сохранить оригинал, сделай копию через slice() или спред-оператор [...arr].

Примеры кода — от простого к сложному

Пример 1: Приветствие для каждого пользователя

// Простейший forEach — просто выполняем действие
const users = ['Аня', 'Ваня', 'Маша'];

users.forEach((name, index) => {
  console.log(`Привет, ${name}! Ты под номером ${index + 1}`);
});
// Вывод:
// Привет, Аня! Ты под номером 1
// Привет, Ваня! Ты под номером 2
// Привет, Маша! Ты под номером 3

Пример 2: Трансформация данных (map)

// У нас есть массив температур в Цельсиях, нужно перевести в Фаренгейты
const celsiusTemps = [0, 10, 20, 30, 40];

// Формула: F = C * 9/5 + 32
const fahrenheitTemps = celsiusTemps.map(temp => {
  return temp * 9/5 + 32;
});

console.log(fahrenheitTemps); // [32, 50, 68, 86, 104]

Пример 3: Фильтрация товаров по цене

const products = [
  { name: 'Книга', price: 500 },
  { name: 'Телефон', price: 25000 },
  { name: 'Ручка', price: 50 },
  { name: 'Ноутбук', price: 80000 }
];

// Оставляем только товары дешевле 1000 рублей
const cheapProducts = products.filter(product => product.price < 1000);

console.log(cheapProducts);
// [
//   { name: 'Книга', price: 500 },
//   { name: 'Ручка', price: 50 }
// ]

Пример 4: Цепочка методов (chaining)

// Берём массив чисел, оставляем чётные, умножаем на 3, сортируем по убыванию
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const result = numbers
  .filter(num => num % 2 === 0)   // [2, 4, 6, 8, 10]
  .map(num => num * 3)            // [6, 12, 18, 24, 30]
  .sort((a, b) => b - a);         // [30, 24, 18, 12, 6]

console.log(result); // [30, 24, 18, 12, 6]

Пример 5: Группировка объектов по свойству (reduce)

const students = [
  { name: 'Аня', grade: 5 },
  { name: 'Ваня', grade: 4 },
  { name: 'Маша', grade: 5 },
  { name: 'Петя', grade: 3 }
];

// Группируем студентов по оценкам
const groupedByGrade = students.reduce((acc, student) => {
  const grade = student.grade;
  // Если такой оценки ещё нет в аккумуляторе, создаём пустой массив
  if (!acc[grade]) {
    acc[grade] = [];
  }
  // Добавляем студента в соответствующую группу
  acc[grade].push(student.name);
  return acc;
}, {}); // Начальное значение — пустой объект

console.log(groupedByGrade);
// {
//   '3': ['Петя'],
//   '4': ['Ваня'],
//   '5': ['Аня', 'Маша']
// }

Пример 6: Поиск уникальных элементов

const numbers = [1, 2, 2, 3, 4, 4, 5, 5, 5];

// Способ 1: через filter и indexOf
const unique1 = numbers.filter((num, index, arr) => {
  // Оставляем только первое вхождение каждого числа
  return arr.indexOf(num) === index;
});
console.log(unique1); // [1, 2, 3, 4, 5]

// Способ 2: через Set (более современный)
const unique2 = [...new Set(numbers)];
console.log(unique2); // [1, 2, 3, 4, 5]

Пример 7: Работа с вложенными массивами (flatMap)

const sentences = [
  'JavaScript это круто',
  'Node.js работает на сервере'
];

// Разбиваем каждое предложение на слова и объединяем в один массив
const words = sentences.flatMap(sentence => sentence.split(' '));
console.log(words);
// ['JavaScript', 'это', 'круто', 'Node.js', 'работает', 'на', 'сервере']

// flatMap = map + flat(1). Если бы мы сделали map, получили бы массив массивов:
const wordsWithMap = sentences.map(s => s.split(' '));
console.log(wordsWithMap);
// [['JavaScript', 'это', 'круто'], ['Node.js', 'работает', 'на', 'сервере']]

Пример 8: Сложная цепочка с проверкой условий

const orders = [
  { id: 1, items: ['книга', 'ручка'], total: 550 },
  { id: 2, items: ['телефон'], total: 25000 },
  { id: 3, items: ['тетрадь', 'карандаш'], total: 120 },
  { id: 4, items: ['ноутбук', 'мышь'], total: 82000 }
];

// Задача: найти названия товаров из заказов дороже 1000 рублей,
// которые состоят из более чем одного товара, и вывести их в верхнем регистре
const expensiveMultiItemProducts = orders
  .filter(order => order.total > 1000)          // Оставляем дорогие заказы
  .filter(order => order.items.length > 1)      // Только с несколькими товарами
  .flatMap(order => order.items)                 // Разворачиваем в плоский массив
  .map(item => item.toUpperCase());              // Переводим в верхний регистр

console.log(expensiveMultiItemProducts);
// ['КНИГА', 'РУЧКА', 'НОУТБУК', 'МЫШЬ']

Сравнение с Python: чем похоже и чем отличается

Если ты уже знаком с Python, тебе будет проще. Многие концепции похожи, но есть важные различия.

Операция JavaScript Python
Добавить в конец arr.push(el) arr.append(el)
Удалить с конца arr.pop() arr.pop()
Преобразовать все элементы arr.map(fn) map(fn, arr) или list comprehension
Отфильтровать arr.filter(fn) filter(fn, arr) или list comprehension
Свести к одному значению arr.reduce(fn, init) functools.reduce(fn, arr, init)
Проверить наличие arr.includes(val) val in arr
Сортировка arr.sort() (мутирует) sorted(arr) (не мутирует)
Срез (подмассив) arr.slice(start, end) arr[start:end]
Длина arr.length len(arr)

Главные отличия:

  • Мутабельность: В JavaScript многие методы (sort, reverse, splice) изменяют исходный массив. В Python большинство операций возвращают новый объект.
  • Callback-функции: В JS ты постоянно передаёшь функции в методы. В Python чаще используют list comprehensions (генераторы списков), которые выглядят лаконичнее.
  • Типизация: Python строже относится к типам в массивах. JS позволяет смешивать типы, но это считается плохой практикой.
  • Производительность: Встроенные методы JS (особенно map, filter, reduce) оптимизированы движком V8 и работают быстрее ручных циклов. В Python list comprehensions обычно быстрее, чем map/filter.
// JavaScript: цепочка методов
const result = [1, 2, 3, 4, 5]
  .filter(x => x > 2)
  .map(x => x * 2);
// [6, 8, 10]
# Python: list comprehension
result = [x * 2 for x in [1, 2, 3, 4, 5] if x > 2]
# [6, 8, 10]

Типичные ошибки и подводные камни

Ошибка 1: Забываешь, что sort() мутирует массив

const original = [3, 1, 2];
const sorted = original.sort();
console.log(original); // [1, 2, 3] — исходный массив ИЗМЕНИЛСЯ!
// Правильно: сделать копию
const copy = [...original].sort();

Ошибка 2: Сортировка чисел без функции сравнения

const nums = [10, 5, 80, 2];
nums.sort(); // По умолчанию сортирует как строки!
console.log(nums); // [10, 2, 5, 80] — неправильно!
// Правильно:
nums.sort((a, b) => a - b); // [2, 5, 10, 80]

Ошибка 3: Путаница между forEach и map

const numbers = [1, 2, 3];
// forEach ничего не возвращает
const result1 = numbers.forEach(num => num * 2);
console.log(result1); // undefined

// map возвращает новый массив
const result2 = numbers.map(num => num * 2);
console.log(result2); // [2, 4, 6]

Ошибка 4: Изменение массива во время итерации

const arr = [1, 2, 3, 4, 5];
// Плохо: удаляем элементы во время forEach
arr.forEach((item, index) => {
  if (item % 2 === 0) {
    arr.splice(index, 1); // Сдвигает индексы!
  }
});
console.log(arr); // [1, 3, 5] — работает, но непредсказуемо
// Лучше: использовать filter
const filtered = arr.filter(item => item % 2 !== 0);

Ошибка 5: Сравнение объектов через includes

const obj1 = { id: 1 };
const obj2 = { id: 1 };
const arr = [obj1];

console.log(arr.includes(obj2)); // false — разные ссылки в памяти
// Для поиска объектов используй find или some
console.log(arr.some(item => item.id === obj2.id)); // true

Ошибка 6: Забываешь про начальное значение в reduce

const numbers = [1, 2, 3];
// Без начального значения первый элемент станет аккумулятором
const sum = numbers.reduce((acc, curr) => acc + curr);
console.log(sum); // 6 — работает, но для пустого массива будет ошибка

// Всегда указывай начальное значение для надёжности
const safeSum = numbers.reduce((acc, curr) => acc + curr, 0);

Лучшие практики и советы профессионалов

  1. Используй методы вместо циклов for. Они делают код короче, читаемее и менее подвержены ошибкам. Циклы for оставь для случаев, когда нужен break или continue.
  2. Соблюдай иммутабельность. Старайся не мутировать исходные данные. Если нужно изменить массив, создавай новый:
// Плохо
const arr = [1, 2, 3];
arr.push(4);

// Хорошо
const newArr = [...arr, 4];
  1. Используй деструктуризацию для ясности.
// Вместо
users.map(function(user) {
  return user.name;
});

// Лучше
users.map(({ name }) => name);
  1. Не злоупотребляй цепочками. 3-4 метода в цепочке — нормально. Если больше — разбей на переменные с понятными названиями.
// Слишком длинная цепочка
const result = data
  .filter(x => x.active)
  .map(x => x.price)
  .filter(x => x > 100)
  .sort((a, b) => a - b)
  .slice(0, 10);

// Лучше разбить
const activeItems = data.filter(x => x.active);
const prices = activeItems.map(x => x.price);
const expensivePrices = prices.filter(x => x > 100);
const sortedPrices = expensivePrices.sort((a, b) => a - b);
const top10 = sortedPrices.slice(0, 10);
  1. Используй flatMap вместо map + flat. Это эффективнее и читаемее.
  2. Для проверки существования элемента используй some, а не filter. some останавливается после первого совпадения, filter проходит весь массив.
  3. Именуй callback-функции осмысленно. Вместо x => x * 2 в сложных случаях используй item => item * 2 или выноси в отдельную функцию.

Вопросы для самопроверки

  1. Вопрос: Чем отличается map от forEach?

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

  2. Вопрос: Какой метод нужно использовать, чтобы найти первый элемент, удовлетворяющий условию?

    Ответ: find(). Он возвращает первый элемент, для которого callback вернул true, или undefined, если такого элемента нет.

  3. Вопрос: Почему [10, 5, 80, 2].sort() даёт неправильный результат?

    Ответ: Потому что sort() по умолчанию сортирует элементы как строки (лексикографически). Числа преобразуются в строки, и "10" оказывается меньше "2", потому что символ "1" меньше "2". Для сортировки чисел нужно передать функцию сравнения: sort((a, b) => a - b).

  4. Вопрос: Что вернёт [1, 2, 3].reduce((acc, curr) => acc + curr) без начального значения?

    Ответ: 6. Без начального значения первый элемент массива становится аккумулятором, а итерация начинается со второго элемента. Но