Перебор массива js с помощью циклов и методов

Перебор массива js выбирают по результату: пройти элементы, создать новый массив, найти значение или остановиться при первом совпадении. В этой статье разобраны циклы, методы массивов, перебор объекта js и асинхронный код. Каждый пример можно запустить в консоли браузера или в Node.js.

Как перебрать массив в JavaScript: какой способ выбрать

Сначала определите результат операции. Циклы подходят для произвольных действий, методы массива сразу показывают намерение кода.

ЗадачаМетодВозвращаетМожно прерватьМеняет исходный массив
Пройти элементы и управлять индексомfor–Да, через breakТолько действиями внутри цикла
Пройти значенияfor...of–Да, через breakТолько действиями внутри цикла
Выполнить действие для каждого элементаforEachundefinedНет штатного раннего выходаСам метод нет; колбэк может
Преобразовать каждый элементmapНовый массивНет штатного раннего выходаСам метод нет; колбэк может
Оставить подходящие элементыfilterНовый массивНет штатного раннего выходаСам метод нет; колбэк может
Свернуть элементы в одно значениеreduceЗначение аккумулятораНет штатного раннего выходаСам метод нет; колбэк может
Найти элементfindЭлемент или undefinedДа, при совпаденииСам метод нет; колбэк может
Найти индексfindIndexИндекс или -1Да, при совпаденииСам метод нет; колбэк может
Проверить хотя бы один элементsomeЛогическое значениеДа, при истинном условииСам метод нет; колбэк может
Проверить все элементыeveryЛогическое значениеДа, при ложном условииСам метод нет; колбэк может

Для простого обхода берите for...of. Для преобразования используйте map, для отбора – filter, для одного ответа – find или reduce.

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

Как перебрать массив по индексам через for

Цикл for дает индекс и полный контроль над остановкой. Такой цикл по массиву в JS удобен для обратного прохода и изменения элементов.

const numbers = [1, 2, 3, 4, 5];
for (let index = 0; index < numbers.length; index += 1) {
  if (numbers[index] === 4) {
    break;
  }
  console.log(numbers[index]);
}
// Результат: 1, 2, 3 (отдельными вызовами)

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

Оператор break завершает цикл, а continue пропускает текущую итерацию. В приведенном примере длина проверяется перед каждым проходом тела цикла.

const values = [10, 20, 30, 40];
for (let index = values.length - 1; index >= 0; index -= 1) {
  console.log(values[index]);
}
// Результат: 40, 30, 20, 10 (отдельными вызовами)

Сохраняйте длину отдельно, если логика требует зафиксировать исходную границу обхода. Само по себе такое сохранение не гарантирует ускорения.

Как перебрать значения массива через for...of

for...of перебирает значения и скрывает техническую работу с индексом. Для большинства задач перебор массива в JavaScript с этим циклом читается проще.

const colors = ['red', 'green', 'blue'];
for (const color of colors) {
  console.log(color);
}
// Результат: red, green, blue (отдельными вызовами)

Индекс можно получить через entries(). Цикл сохраняет break и continue, поэтому подходит для раннего выхода.

const users = ['Анна', 'Борис', 'Вера'];
for (const [index, user] of users.entries()) {
  if (index === 1) {
    continue;
  }
  console.log(`${index}: ${user}`);
}
// Результат: 0: Анна, затем 2: Вера

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

Как работает forEach в JavaScript

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

const numbers = [2, 4, 6];
numbers.forEach((value, index, array) => {
  console.log(value, index, array === numbers);
});
// Результат: 2 0 true, затем 4 1 true, затем 6 2 true

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

const names = ['Лена', 'Олег'];
const result = names.forEach((name) => name.toUpperCase());
console.log(result);
// Результат: undefined

return завершает только текущий колбэк. Остановить весь forEach оператором break нельзя. Для нового массива берите map, а для раннего выхода – цикл или find.

Подробное описание метода есть в справке forEach на MDN.

Как преобразовать массив через map, filter и reduce

Три метода возвращают разные результаты: map преобразует, filter отбирает, reduce сворачивает. Сами методы не изменяют исходный массив, но действия колбэков могут его изменить.

map создает новый массив

const prices = [100, 200, 300];
const withTax = prices.map((price) => price * 1.2);
console.log(withTax);
// Результат: [120, 240, 360]

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

filter оставляет подходящие элементы

const numbers = [1, 2, 3, 4, 5, 6];
const even = numbers.filter((number) => number % 2 === 0);
console.log(even);
// Результат: [2, 4, 6]

Методы можно объединять. Сначала фильтр оставит активные записи, затем map возьмет из них названия.

const users = [
  { name: 'Анна', active: true },
  { name: 'Борис', active: false },
  { name: 'Вера', active: true }
];
const activeNames = users.filter((user) => user.active).map((user) => user.name);
console.log(activeNames);
// Результат: ['Анна', 'Вера']

Метод filter в JavaScript отбирает элементы массива по условию. Колбэк сохраняет элемент, если возвращает истинное в логическом контексте значение. В примере он возвращает true или false.

Не меняйте элементы внутри фильтра: оставьте колбэку только проверку условия.

reduce получает одно значение

const numbers = [3, 5, 7];
const sum = numbers.reduce((total, number) => total + number, 0);
console.log(sum);
// Результат: 15

Начальное значение 0 задает аккумулятор для первого вызова колбэка. На пустом массиве этот расчет тоже вернет 0. Без начального значения reduce на пустом массиве выбросит TypeError.

Для группировки аккумулятором может быть объект.

const words = ['a', 'bb', 'c'];
const grouped = words.reduce((result, word) => {
  const length = word.length;
  result[length] ??= [];
  result[length].push(word);
  return result;
}, {});
console.log(grouped);
// Результат: {1: ['a', 'c'], 2: ['bb']}

В этом примере колбэк изменяет отдельный объект-аккумулятор. Исходный массив words остается прежним.

Как найти и проверить элементы через find, findIndex, some и every

Эти методы сами останавливаются, когда ответ уже найден. Они не создают новый массив и сами не изменяют исходный.

const users = ['Анна', 'Борис', 'Вера'];
const user = users.find((name) => name.startsWith('В'));
const index = users.findIndex((name) => name.startsWith('В'));
console.log(user, index);
// Результат: Вера 2

find возвращает элемент или undefined. findIndex возвращает индекс или -1. Проверяйте найденный индекс явным сравнением с -1.

const names = ['Анна', 'Борис'];
const index = names.findIndex((name) => name === 'Анна');
console.log(index, index !== -1);
// Результат: 0 true

Условие if (index) даст неверную проверку наличия элемента: индекс 0 ложный, а -1 истинный.

const scores = [42, 67, 81];
const hasHighScore = scores.some((score) => score >= 80);
const allPassed = scores.every((score) => score >= 40);
console.log(hasHighScore, allPassed);
// Результат: true true

some ищет хотя бы одно совпадение, а every требует совпадения для всех посещаемых элементов. На пустом массиве some возвращает false, а every – true.

Методы findLast и findLastIndex выполняют поиск с конца массива.

Для справки по сигнатурам используйте страницы find, some и every на MDN.

Как прервать перебор массива досрочно

Оператор break завершает циклы for и for...of. Выйти из метода массива через break внутри его колбэка нельзя.

const numbers = [2, 4, 7, 8];
let firstOdd;
for (const number of numbers) {
  if (number % 2 !== 0) {
    firstOdd = number;
    break;
  }
}
console.log(firstOdd);
// Результат: 7

Для проверки условия замените ручной цикл на some или every. Если нужен сам найденный элемент, используйте find.

const numbers = [2, 4, 7, 8];
const hasOdd = numbers.some((number) => number % 2 !== 0);
console.log(hasOdd);
// Результат: true

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

Перебор объекта js через Object.keys, Object.values и Object.entries

Способ перебора объекта в JavaScript зависит от нужных данных: ключей, значений или пар. Методы Object.keys, Object.values и Object.entries ограничивают обход его собственными перечисляемыми строковыми свойствами.

Object.keys и Object.values

const user = { name: 'Ирина', age: 28 };
for (const key of Object.keys(user)) {
  console.log(key, user[key]);
}
console.log(Object.values(user));
// Результат: name Ирина, age 28, затем ['Ирина', 28]

Object.keys возвращает ключи, а Object.values – соответствующие значения. Эти методы не меняют объект и не включают свойства с ключами-символами.

Object.entries и деструктуризация

const settings = { theme: 'dark', language: 'ru' };
for (const [key, value] of Object.entries(settings)) {
  console.log(`${key}=${value}`);
}
// Результат: theme=dark, language=ru (отдельными вызовами)

Конструкция позволяет перебрать объект парами ключ–значение. Деструктуризация записывает каждую пару в переменные key и value.

У обычного объекта эти методы сначала обрабатывают ключи-индексы массива по возрастанию. Остальные перечисляемые строковые ключи следуют в порядке создания свойств. Строки '01' и '-1' не относятся к ключам-индексам массива.

const labels = { '01': 'с нулем', 10: 'десять', 2: 'два', name: 'имя' };
console.log(Object.keys(labels));
// Результат: ['2', '10', '01', 'name']

for...in и наследуемые свойства

const user = Object.create({ role: 'reader' });
user.name = 'Ирина';
for (const key in user) {
  if (Object.hasOwn(user, key)) {
    console.log(key, user[key]);
  }
}
// Результат: name Ирина

for...in перечисляет строковые ключи, включая унаследованные перечисляемые свойства. Проверка Object.hasOwn оставляет только свойства самого объекта.

Для обычных объектов без изменений во время обхода сначала идут собственные свойства, затем свойства прототипов. На каждом уровне ключи-индексы идут по возрастанию, остальные строки – по порядку создания. Повторяющиеся имена свойств цикл повторно не выдает.

Подробности перечисления свойств описаны в справке for...in на MDN.

Почему массив не перебирают через for...in

Массив хранит элементы по числовым индексам, но for...in возвращает ключи строками. В обход могут попасть дополнительные свойства и свойства прототипа.

const numbers = [10, 20];
numbers.extra = 'служебное';
for (const key in numbers) {
  console.log(key, typeof key);
}
// Результат: 0 string, 1 string, extra string

Индексные ключи перечисляются по возрастанию, но цикл обходит свойства, включая дополнительные. Пустые позиции массива он пропускает. Поэтому for...in не заменяет последовательный обход всех позиций.

Для массива берите for, for...of или метод массива.

Перебор массива с async/await

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

const ids = [1, 2, 3];
const load = async (id) => id * 10;
const values = [];
ids.forEach(async (id) => {
  values.push(await load(id));
});
console.log(values.length);
// Результат сразу после forEach: 0

Даже await ids.forEach(...) не дождется всех колбэков: метод возвращает undefined.

Последовательный перебор выполняйте через for...of. Следующая итерация начнется после завершения текущего промиса.

const run = async () => {
  const ids = [1, 2, 3];
  const load = async (id) => id * 10;
  const values = [];
  for (const id of ids) {
    values.push(await load(id));
  }
  console.log(values);
};
run().catch(console.error);
// Результат: [10, 20, 30]

Независимые асинхронные операции запускайте совместно через map и Promise.all.

const run = async () => {
  const ids = [1, 2, 3];
  const load = async (id) => id * 10;
  const values = await Promise.all(ids.map((id) => load(id)));
  console.log(values);
};
run().catch(console.error);
// Результат: [10, 20, 30]

Promise.all сохраняет порядок результатов по исходному массиву промисов. Если один промис отклонится, общий промис тоже отклонится. Уже запущенные операции при этом автоматически не отменяются.

Большую очередь запросов обрабатывайте с ограничением одновременных операций. Promise.all сам такого ограничения не задает.

Производительность методов перебора

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

Для обычных списков выбирайте метод по смыслу.

Цикл for дает контроль над индексом, остановкой и количеством действий. for...of сохраняет управление, но делает код короче. Цепочка filter().map() создает промежуточный массив и проходит отобранные элементы повторно.

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

Измеряйте конкретную задачу в целевой среде на одинаковых данных. Уберите console.log из измеряемого участка и повторите измерения после прогрева. Результат одного браузера нельзя автоматически переносить на другую среду.

Ошибки при переборе массива

Ошибки чаще возникают из-за неверного ожидания от метода. Сверяйте возвращаемое значение, действия колбэка и поведение разреженного массива.

Изменение массива во время forEach

Замена значений по существующим индексам сама по себе не сдвигает элементы.

const numbers = [1, 2, 3];
numbers.forEach((number, index, array) => {
  array[index] = number * 2;
});
console.log(numbers);
// Результат: [2, 4, 6]

Такой код меняет исходный массив. Для отдельного массива результатов используйте map. Однако map не копирует вложенные объекты автоматически: они могут оставаться общими для обоих массивов.

const users = [{ name: 'Анна' }];
const copied = users.map((user) => user);
copied[0].name = 'Вера';
console.log(users[0].name);
// Результат: Вера

Структурные изменения опаснее: удаление элемента со сдвигом может пропустить соседнее значение.

const numbers = [1, 2, 3];
const visited = [];
numbers.forEach((number, index, array) => {
  visited.push(number);
  if (index === 0) {
    array.shift();
  }
});
console.log(visited);
// Результат: [1, 3]
console.log(numbers);
// Результат: [2, 3]

После удаления первого элемента число 2 перемещается на индекс 0. Следующая итерация уже читает индекс 1, где находится 3.

forEach фиксирует длину перед началом обхода. Добавленные за исходной границей элементы он не посещает. Измененные значения внутри границы метод читает при достижении соответствующего индекса.

Для удаления элементов безопаснее создать результат через filter, не меняя источник внутри колбэка.

const numbers = [1, 2, 3];
const remaining = numbers.filter((number) => number !== 1);
console.log(remaining);
// Результат: [2, 3]
console.log(numbers);
// Результат: [1, 2, 3]

map без return

const numbers = [1, 2, 3];
const doubled = numbers.map((number) => {
  number * 2;
});
console.log(doubled);
// Результат: [undefined, undefined, undefined]

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

Асинхронный колбэк в forEach

Асинхронный колбэк не превращает forEach в ожидаемую цепочку операций. Используйте for...of с await, если действия зависят друг от друга. Для независимых операций соберите промисы через map и дождитесь Promise.all.

Разреженные массивы

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

const sparse = [1, , 3];
for (const value of sparse) {
  console.log(value);
}
sparse.forEach((value) => console.log(value));
// Результат for...of: 1, undefined, 3; forEach: 1, 3

Обычный индексный for проходит все позиции до length. Чтение отсутствующего элемента дает undefined, как при for...of.

map пропускает вызов колбэка для пустой позиции, но сохраняет ее в результате. Длина нового массива тоже сохраняется. filter собирает посещенные подходящие элементы без дырок.

const sparse = [1, , 3];
const mapped = sparse.map((value) => value * 2);
const filtered = sparse.filter(() => true);
console.log(mapped.length, Object.hasOwn(mapped, 1));
// Результат: 3 false
console.log(mapped[1]);
// Результат: undefined при чтении отсутствующей позиции
console.log(filtered);
// Результат: [1, 3]
СпособПоведение пустых позиций
Индексный for без дополнительных проверокПроходит позиции; чтение дырки дает undefined
for...ofВыдает undefined на месте дырки
forEach, some, everyНе вызывают колбэк для дырок
mapПропускает колбэк, сохраняет длину и дырки
filterПропускает дырки, собирает плотный результат
reduceПропускает дырки; без начального значения ищет первый существующий элемент
find, findIndex, findLast, findLastIndexВызывают колбэк для дырок со значением undefined

Отказ от var

В примерах используются const и let. Они ограничивают область видимости переменной блоком, поэтому уменьшают риск случайного переиспользования счетчика. Объявление var не создает такую блочную область видимости.

Как выбрать способ перебора массива под задачу

Сначала определите ограничения обработки: порядок действий, возможность остановки и допустимость изменений источника.

  • Если обработка зависит от предыдущего элемента или требует остановки, используйте for...of.
  • Если нужен обратный проход или нестандартный шаг индекса, берите for.
  • Если результат нужен для дальнейшей обработки, возвращайте его через map, filter или reduce.
  • Если достаточно ответа «да» или «нет», используйте some либо every: они остановятся после определения ответа.
  • Если нужен найденный элемент, выбирайте find; для его позиции используйте findIndex и сравнение с -1.
  • Если асинхронные действия зависят друг от друга, ожидайте их последовательно через for...of. Для независимых действий используйте Promise.all с map.

Для собственных перечисляемых строковых свойств объекта удобно использовать Object.entries. Добавляйте for...in, когда задача требует учитывать цепочку прототипов.

Совет от нашей редакции: перед выбором метода запишите ожидаемый результат на маленьком массиве. Добавьте пустой массив и пропуск элемента – такие примеры помогают заметить неверные ожидания от обхода.

Комментарии

Оставьте свой комментарий
Пока никто не оставил комментариев