Методы массивов и коллекций в 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);
Лучшие практики и советы профессионалов
- Используй методы вместо циклов for. Они делают код короче, читаемее и менее подвержены ошибкам. Циклы for оставь для случаев, когда нужен
breakилиcontinue. - Соблюдай иммутабельность. Старайся не мутировать исходные данные. Если нужно изменить массив, создавай новый:
// Плохо
const arr = [1, 2, 3];
arr.push(4);
// Хорошо
const newArr = [...arr, 4];
- Используй деструктуризацию для ясности.
// Вместо
users.map(function(user) {
return user.name;
});
// Лучше
users.map(({ name }) => name);
- Не злоупотребляй цепочками. 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);
- Используй
flatMapвместоmap+flat. Это эффективнее и читаемее. - Для проверки существования элемента используй
some, а неfilter.someостанавливается после первого совпадения,filterпроходит весь массив. - Именуй callback-функции осмысленно. Вместо
x => x * 2в сложных случаях используйitem => item * 2или выноси в отдельную функцию.
Вопросы для самопроверки
-
Вопрос: Чем отличается
mapотforEach?Ответ:
mapвозвращает новый массив с преобразованными элементами, аforEachпросто выполняет функцию для каждого элемента и возвращаетundefined.mapиспользуют для трансформации данных,forEach— для побочных эффектов (вывод в консоль, запись в БД). -
Вопрос: Какой метод нужно использовать, чтобы найти первый элемент, удовлетворяющий условию?
Ответ:
find(). Он возвращает первый элемент, для которого callback вернулtrue, илиundefined, если такого элемента нет. -
Вопрос: Почему
[10, 5, 80, 2].sort()даёт неправильный результат?Ответ: Потому что
sort()по умолчанию сортирует элементы как строки (лексикографически). Числа преобразуются в строки, и "10" оказывается меньше "2", потому что символ "1" меньше "2". Для сортировки чисел нужно передать функцию сравнения:sort((a, b) => a - b). -
Вопрос: Что вернёт
[1, 2, 3].reduce((acc, curr) => acc + curr)без начального значения?Ответ: 6. Без начального значения первый элемент массива становится аккумулятором, а итерация начинается со второго элемента. Но