forEach JS: как работает метод перебора массива в JavaScript

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

Что такое forEach в JavaScript

forEach — это метод массива, который вызывает указанную функцию один раз для каждого существующего элемента массива в порядке возрастания индекса. Проще говоря, он проходит по массиву как контролер по вагонам: заходит с первого элемента, проверяет каждый по списку и завершает работу на последнем.

Синтаксически метод выглядит просто, но именно из-за этой простоты его часто используют не по назначению. Он не возвращает преобразованный результат, как map(), не фильтрует элементы, как filter(), и не умеет остановиться посередине, как for с break. Его главная задача — выполнить побочный эффект: вывести данные в консоль, обновить DOM, накопить значения во внешней переменной, вызвать функцию, отправить запрос.

const numbers = [1, 2, 3];

numbers.forEach(function(item, index, array) {
  console.log(item, index);
});

У callback-функции есть три стандартных аргумента:

  1. item — текущий элемент массива;
  2. index — индекс текущего элемента;
  3. array — сам массив целиком.

Согласно спецификации ECMAScript, forEach() вызывает callback только для тех индексов, которые реально существуют в массиве. Это значит, что «дырявые» массивы обрабатываются не так, как многие ожидают.

const arr = [1, , 3];

arr.forEach((item, index) => {
  console.log(index, item);
});

// Выведет:
// 0 1
// 2 3

Пропущенный элемент с индексом 1 callback не получит, потому что его фактически нет в массиве.

Как работает метод forEach JS: пошаговый маршрут выполнения

forEach проходит по массиву слева направо, берет каждый существующий элемент по индексу и передает его в callback-функцию. Это не абстрактный «перебор», а четкая последовательность вызовов с понятными остановками, началом и концом.

Если представить выполнение как маршрут, он выглядит так:

Этап Что происходит Деталь
Начало маршрута Метод вызывается у массива Например, у массива длиной 5
Остановка 0 Берется элемент с индексом 0 Если элемент существует, callback выполняется
Остановка 1 Берется элемент с индексом 1 Если индекс пустой, шаг пропускается
Остановка 2 Берется элемент с индексом 2 Передаются value, index, array
Перебор продолжается До последнего индекса
Конец маршрута Метод завершает работу Возвращает undefined

Для массива [10, 20, 30, 40] маршрут будет конкретным:

  1. Начало: индекс 0, значение 10.
  2. Следующая остановка: индекс 1, значение 20.
  3. Следующая остановка: индекс 2, значение 30.
  4. Последняя остановка: индекс 3, значение 40.
  5. Завершение: метод возвращает undefined.
const prices = [10, 20, 30, 40];

prices.forEach((price, index) => {
  console.log(`Индекс: ${index}, цена: ${price}`);
});

Важно понимать: стоимость такого «маршрута» по времени линейная — O(n), где n — количество элементов массива. Это означает, что если элементов 10, callback будет вызван до 10 раз; если 1000 — до 1000 раз. Именно поэтому forEach хорошо подходит для стандартной последовательной обработки, но не решает задачи раннего выхода.

Когда я проверяю код начинающих разработчиков, чаще всего вижу одну и ту же ошибку: они ждут от forEach поведения как у обычного цикла for. На практике это разные инструменты. forEach удобен там, где маршрут должен пройти до конечной остановки без досрочного схода.

Синтаксис forEach: параметры callback и thisArg

Метод forEach принимает callback-функцию и необязательный параметр thisArg, который задает значение this внутри callback. На практике чаще всего используют стрелочные функции, поэтому про thisArg нередко забывают.

Общий синтаксис:

array.forEach(callback(currentValue, index, array), thisArg);

Параметры callback-функции

Каждый параметр решает конкретную задачу:

  • currentValue — текущий элемент;
  • index — его индекс;
  • array — ссылка на исходный массив.
const users = ['Анна', 'Олег', 'Мария'];

users.forEach((user, index, arr) => {
  console.log(user);
  console.log(index);
  console.log(arr.length);
});

Когда нужен thisArg

thisArg нужен в тех случаях, когда callback — обычная функция и внутри нее требуется доступ к контексту объекта. Если используется стрелочная функция, она берет this из внешней области видимости, и thisArg обычно не нужен.

const counter = {
  sum: 0,
  add(numbers) {
    numbers.forEach(function(num) {
      this.sum += num;
    }, this);
  }
};

counter.add([1, 2, 3]);
console.log(counter.sum);

В этом примере без передачи this вторым аргументом обычная функция не смогла бы корректно обращаться к свойству sum.

Что возвращает forEach и почему это важно

forEach всегда возвращает undefined, потому что его задача — выполнить действие для каждого элемента, а не построить новый результат. Это ключевое отличие от методов преобразования массивов.

const result = [1, 2, 3].forEach(item => item * 2);

console.log(result); // undefined

Именно здесь возникает одна из самых популярных ошибок: разработчик пытается использовать forEach как map.

Сравнение forEach, map, filter и reduce

Метод Что делает Возвращает Можно ли остановить досрочно
forEach Выполняет действие для каждого элемента undefined Нет
map Преобразует каждый элемент Новый массив Нет
filter Оставляет подходящие элементы Новый массив Нет
reduce Сводит массив к одному значению Любой тип Нет
for…of Перебирает значения Ничего автоматически Да

Если нужен новый массив, берите map. Если нужен эффект — например, отрисовать элементы на странице или записать лог — подойдет forEach.

Когда использовать forEach, а когда лучше выбрать другой цикл

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

Подходящие сценарии для forEach

  1. Вывод значений в консоль.
  2. Рендер списка в интерфейсе.
  3. Накопление данных во внешней переменной.
  4. Вызов побочных функций для каждого элемента.
  5. Обновление свойств объектов на месте.
const products = [
  { name: 'Ноутбук', active: true },
  { name: 'Планшет', active: false }
];

products.forEach(product => {
  product.checked = true;
});

console.log(products);

Когда forEach — не лучший выбор

  1. Нужно прервать цикл при выполнении условия.
  2. Нужно дождаться асинхронных операций по порядку.
  3. Нужно получить новый массив.
  4. Нужен максимально контролируемый цикл с break и continue.

Практическое наблюдение: в рабочих проектах forEach часто используют для сетевых запросов внутри callback, а затем удивляются, почему код работает «не по очереди». Причина в том, что сам forEach не ожидает завершения async/await так, как ожидают многие разработчики.

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

Асинхронность и forEach: почему await внутри callback часто подводит

forEach не умеет дожидаться выполнения async callback по порядку, поэтому await внутри него не превращает перебор в последовательный асинхронный цикл. Это один из самых важных практических нюансов метода.

Проблемный пример:

const ids = [1, 2, 3];

ids.forEach(async (id) => {
  await fetch(`/api/user/${id}`);
  console.log(`Загружен пользователь ${id}`);
});

console.log('Готово');

Строка ‘Готово’ выведется раньше, чем завершатся запросы. Более того, сами запросы стартуют почти одновременно, а не строго один за другим.

Как сделать последовательно

const ids = [1, 2, 3];

async function loadUsers() {
  for (const id of ids) {
    await fetch(`/api/user/${id}`);
    console.log(`Загружен пользователь ${id}`);
  }

  console.log('Готово');
}

loadUsers();

С точки зрения когнитивной нагрузки это тоже важно. Мозг разработчика ожидает, что «для каждого элемента» значит «сделай и дождись». Но forEach работает как диспетчер, который раздает задания, а не контролирует завершение каждого на маршруте.

Типичные ошибки при работе с методом перебора массива forEach в JavaScript

Основные ошибки при использовании forEach связаны с неправильным ожиданием возврата значения, попыткой прервать цикл и некорректной работой с асинхронным кодом. Если знать эти ограничения заранее, метод становится предсказуемым и безопасным.

Ошибка 1. Ожидание нового массива

const numbers = [1, 2, 3];
const doubled = numbers.forEach(num => num * 2);

console.log(doubled); // undefined

Правильно:

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

console.log(doubled); // [2, 4, 6]

Ошибка 2. Попытка остановить цикл через break

Внутри forEach нельзя использовать break или continue, потому что это не синтаксическая конструкция цикла, а вызов метода.

Если нужно остановиться на значении 5, лучше использовать for…of:

for (const num of [1, 2, 3, 4, 5, 6]) {
  if (num === 5) break;
  console.log(num);
}

Ошибка 3. Надежда на return

return внутри callback завершает только текущий вызов callback, а не весь forEach.

[1, 2, 3, 4].forEach(num => {
  if (num === 2) return;
  console.log(num);
});

// Выведет 1, 3, 4

Ошибка 4. Изменение массива во время обхода

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

const arr = [1, 2, 3];

arr.forEach((item, index) => {
  console.log(item);
  if (index === 0) {
    arr.push(4);
  }
});

// 4 не будет обработан в текущем forEach

Практические примеры: forEach на массивах чисел, строк и объектов

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

Пример 1. Подсчет суммы через внешний аккумулятор

const numbers = [5, 10, 15];
let sum = 0;

numbers.forEach(num => {
  sum += num;
});

console.log(sum); // 30

Пример 2. Формирование HTML-разметки списка

const fruits = ['Яблоко', 'Груша', 'Манго'];
let html = '';

fruits.forEach(fruit => {
  html += `<li>${fruit}</li>`;
});

console.log(html);

Пример 3. Обновление объектов в массиве

const orders = [
  { id: 101, status: 'new' },
  { id: 102, status: 'new' }
];

orders.forEach(order => {
  order.status = 'processed';
});

console.log(orders);

Пример 4. Использование индекса

const tasks = ['Задача 1', 'Задача 2', 'Задача 3'];

tasks.forEach((task, index) => {
  console.log(`${index + 1}. ${task}`);
});

На практике разработчики часто используют forEach именно для мутации объектов в пределах одного массива. Это быстро и читаемо, если изменение осознанное. Но если проект придерживается иммутабельного подхода, лучше перейти к map с созданием новых объектов.

Производительность, читаемость и особенности спецификации

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

Согласно данным MDN и спецификации ECMAScript, метод:

  • не вызывает callback для пустых слотов массива;
  • не изменяет исходный массив сам по себе;
  • может работать с массивоподобными объектами через call;
  • выполняется в порядке возрастания индексов.

Пример с массивоподобным объектом

const arrayLike = {
  0: 'A',
  1: 'B',
  length: 2
};

Array.prototype.forEach.call(arrayLike, item => {
  console.log(item);
});

Если говорить о производительности, в реальных интерфейсах разница между forEach, for…of и обычным for чаще всего менее важна, чем качество архитектуры, объем перерисовок DOM или количество сетевых запросов. Поэтому выбирать стоит не «самый быстрый цикл вообще», а наиболее подходящий инструмент под задачу.

Краткие рекомендации по выбору

  1. Используйте forEach, если нужен побочный эффект.
  2. Используйте map, если нужен новый массив.
  3. Используйте for…of, если нужен await или break.
  4. Используйте reduce, если нужно собрать одно итоговое значение.

Нестандартная, но точная аналогия: forEach — это не конвейер упаковки, который выдает новую коробку на выходе, а ряд кнопок вызова, нажимаемых по очереди. Он запускает действия, но не производит итоговый контейнер с результатом.

Краткий итог по forEach в JavaScript

forEach — это метод перебора массива, предназначенный для последовательного выполнения действия над каждым элементом без возврата нового массива. Он прост, читаем, хорошо подходит для побочных эффектов и плохо подходит для преобразований, остановки по условию и последовательной асинхронной логики.

Если запомнить три правила — не возвращает результат, не прерывается через break, не ждет async по порядку — метод перестает быть источником ошибок и становится надежным рабочим инструментом. Для задач обхода данных это один из самых удобных методов JavaScript, когда нужен именно понятный и прямолинейный маршрут от первого элемента массива до последнего.

ChatGPT Perplexity Google (AI)