Недавно я проходил собеседование, которое включало сравнение двух разных схем. Опущу детали, но прямо в середине собеседования всплыла одна очень важная вещь — нельзя прерывать цикл forEach(). Я забыл об этом, и, видимо, похоронил свои шансы получить работу. Я надеюсь, после прочтения этой статьи вы не повторите мою ошибку.
Как отмечает MDN:
Не существует другого способа остановить или прервать цикл forEach() loop кроме выброса исключения. Если вам нужно подобное поведение, метод forEach() неподходящий инструмент
Звучит весьма категорично. Однако это правда: важно знать, какой инструмент выбрать.
Перед тем, как разобраться, почему нельзя прерывать forEach()
, давайте рассмотрим, что такое цикл и откуда появился forEach()
.
Циклы в программировании решают очень распространённую задачу: запускать тот же самый код для всех данных. Проще говоря:
Повторение того же кода снова и снова (в цикле), пока мы не достигнем конечного состояния.
Для сравнения решим задачу, используя различные виды циклов. Вот она: сравнить два массива и посмотреть, совпадают ли их элементы.
Вот данные, которые мы будем сравнивать:
const jedis = ["Anakin","Luke"]
const sith = ["Palpatine", "Anakin"]
У нас есть два массива, оба с парой имён. В обоих списках есть Энакен. Это простейшая задача, однако она не так далека от того, которую я решал на собеседовании.
Я не собираюсь рассказывать, что один цикл лучше других. Все они предлагают уникальные программные решения и имеют своё применение. Хитрость в том, чтобы знать, когда и какой цикл использовать.
Если вы когда-либо проходили курс по программированию, вы знакомы со старым добрым циклом for
. Долгое время он был полезным инструментом для программистов, да и сейчас полезен. Давайте решим нашу задачу с его помощью:
// снова наши данные для справки
const jedis = ["Anakin", "Luke"];
const sith = ["Palpatine", "Anakin"];
// запускаем цикл, определяем переменную итератора
for (let i = 0; i < jedis.length; i++) {
// создаём переменную, на которую можем ссылаться
const thisJedi = jedis[i];
// проверяем, существует ли элемент в проверяемом массиве
if (sith.includes(thisJedi)) {
// если существует, значит, этот джедай ещё и ситх
console.log(`${thisJedi} is also a Sith`);
// можем выходить
break;
}
console.log(`${thisJedi} is not a Sith`);
}
Цикл for
предлагает весьма удобный способ выхода, если он удовлетворяет выбранному условию, что чрезвычайно полезно при циклической обработке большого количества данных. Он также был очень полезен в решении некоторых задач Проекта Эйлера, особенно этой.
forEach()
был представлен в спецификации в 2009 году наряду с прочими достоинствами ES5. Он служит удобным методом написания чистого кода, который легко перебирает элементы массива.
Цикл forEach()
— это функция, запускающая другую функцию (обратный вызов) для каждого элемента массива. Мы определяем, что происходит в этой функции обратного вызова. JS принимает три параметра для этой функции:
Давайте используем цикл forEach()
для решения нашей задачи. Я включил все три параметра в функцию, но используем мы только первый — элемент, который я назвал jedi
.
// Создаём глобальную переменную состояния для отслеживания
let matching
// запускаем цикл в массиве
jedis.forEach((jedi,index,array) => {
// проверяем, есть ли элемент jedi в массиве sith
if(!sith.includes(jedi)) {
// если нет, задаём значение false глобальной переменной
matching = false
}
// код продолжает работу...
})
console.log(matching) // false
Наше решение по сути делает то же самое. Единственное отличие в том, что код продолжает работать, пока не достигнет конца массива jedis
. Навряд ли для такого маленького массива будет видна разница в производительности.
И наконец мы добрались до ответа на наш вопрос: почему нельзя прерывать цикл forEach()
? Потому что цикл запускает функцию обратного вызова для каждого элемента, поэтому, даже если вы пишете return, он возвращается только в этом инстансе функции. Он продолжит работать. В случае функции forEach()
ничего не произойдёт с возвращённым кодом. Имейте в виду, что это не так в других методах массива.
Из-за этого выражения break
или continue
также являются не допустимыми.
Существует довольно много различных циклов. Все они имеют различное применение, и я рекомендую ознакомиться с каждым из них. Вам не всегда нужен именно цикл forEach()
.
Вероятно, наиболее распространёнными методами в руководствах являются forEach()
и map()
. Самое существенное различие между ними в том, что map
возвращает новый массив, а forEach()
— нет.
Цикл while
Array.forEach(), Array.map(), Array.filter(), Array.reduce(), Array.reduceRight(), Array.every(), Array.some(), Array.indexOf(), Array.lastIndexOf(), Array.find(), Array.findIndex()
Повторяемые циклы объектов (включая массивы)for in, for of
Как упоминалось ранее, в документации MDN, выбор правильного инструмента крайне важен для успеха. На первый взгляд количество вариантов ошеломляет, но мне нравится подход: “если инструмент работает, значит, он подходит”.
Вы можете рефакторить свой код до посинения, но будете просто тратить время вместо того, чтобы создавать. На собеседовании я использовал правильный инструмент, но неправильным способом. Если бы я помнил, что цикл forEach()
нельзя прервать, всё могло бы сложиться иначе ????????♂️.
Хорошего кодинга!
Перевод статьи Jared Nutt: Why you can’t break a forEach loop in JavaScript
Комментарии