![image](https://habrastorage.org/getpro/habr/post_images/5b6/198/12e/5b619812ee92c0623a7b29b670592bbc.png)
Цикл
for
хорошо послужил нам, но он устарел и должен уступить место более новым, функциональным техникам программирования.К счастью, этот факт не требует от вас быть мастером функционального программирования, более того, это то, что вы можете использовать в своих текущих проектах прямо сегодня!
Так в чем проблема цикла for в JavaScript?
Дизайн цикла
for
подталкивает к мутациям состояния (англ. mutation of state — изменение состояния — прим. переводчика) и применению сайд эффектов (англ. side effects — побочные эффекты — прим. переводчика), которые являются потенциальными источниками багов и непредсказуемого поведения кода.Все мы слышали, что глобальное состояние — это плохо, и что мы должны избегать его. Однако, локальное состояние разделяет те же проблемы что и глобальное, мы просто не сталкиваемся с ними так часто, ведь они проявляются в меньших масштабах. Фактически, мы никогда не решали проблему, а просто сводили ее к минимуму.
Однажды, используя мутабельное состояние (англ. mutable state — изменяемое состояние — прим. переводчика), значение случайной переменной изменится по неизвестной причине, и вы потратите часы на отладку и поиск причины изменения. У меня волосы на голове встают дыбом, только от одной мысли об этом.
Я бы хотел немного поговорить о сайд эффектах. Эти слова даже звучат ужасно, сайд эффекты. Дрянь. Вы хотите чтобы в ваших программах были сайд эффекты? Нет, я не хочу сайд эффектов в моих программах!
Но что такое сайд эффекты?
Считается, что у функции есть сайд эффекты если она модифицирует что-то за пределами своей области видимости. Это может быть изменение переменной, пользовательский ввод, запрос к api, запись данных на диск, лог в консоль и т.д.
Сайд эффекты — действительно мощный инструмент, но с большой силой приходит большая ответственность.
Сайд эффекты, по возможности, должны быть устранены или хотя бы инкапсулированы так, чтобы мы могли их контролировать. Функции с сайд эффектами тяжелее читать и тестировать, избегайте их всегда, когда это возможно. К счастью, в рамках данной статьи, мы не будем беспокоиться о побочных эффектах.
Меньше слов, больше кода. Давайте посмотрим на типичный цикл
for
, который вы вероятно видели сотни раз.const cats = [
{ name: 'Mojo', months: 84 },
{ name: 'Mao-Mao', months: 34 },
{ name: 'Waffles', months: 4 },
{ name: 'Pickles', months: 6 }
]
var kittens = []
// Типичный, плохо написанный цикл for
for (var i = 0; i < cats.length; i++) {
if (cats[i].months < 7) {
kittens.push(cats[i].name)
}
}
console.log(kittens)
Я собираюсь отрефакторить этот код шаг за шагом, чтобы вы смогли пронаблюдать как легко превратить ваш собственный код в нечто более прекрасное.
Во-первых, я извлеку условное выражение в отдельную функцию:
const isKitten = cat => cat.months < 7
var kittens = []
for (var i = 0; i < cats.length; i++) {
if (isKitten(cats[i])) {
kittens.push(cats[i].name)
}
}
Выносить условия — это в целом хорошая практика. Изменение фильтрации с “меньше чем 7 месяцев” на “является ли котенком” — большой шаг вперед. Теперь код передает наши намерения гораздо лучше. Почему мы берем котов до 7 месяцев? Не совсем понятно. Мы хотим найти котят, так пусть код говорит об этом!
Другая польза в том, что
isKitten
теперь можно переиспользовать, а все мы знаем, переиспользование кода всегда должно быть нашей целью.Следующее изменение — извлечь преобразование (или отображение) кота его в имя. Это изменение будет понятнее позднее, а сейчас просто доверьтесь мне.
const isKitten = cat => cat.months < 7
const getName = cat => cat.name
var kittens = []
for (var i = 0; i < cats.length; i++) {
if (isKitten(cats[i])) {
kittens.push(getName(cats[i]))
}
}
Я собирался написать несколько абзацев для описания механики работы
filter
и map
, но вместо этого, я покажу вам как легко читать и понимать этот код, даже увидев их (filter
и map
— прим. переводчика) впервые. Это лучшая демонстрация того, насколько читабельным может стать ваш код.const isKitten = cat => cat.months < 7
const getName = cat => cat.name
const kittens =
cats.filter(isKitten)
.map(getName)
Обратите внимание, мы избавились от
kittens.push(...)
. Никаких больше мутаций состояния и никаких var
.Код отдающий предпочтение const
перед var
и let
выглядит чертовски привлекательно
Конечно, мы могли использовать
const
с самого начала, так как он не делает сам объект иммутабельным (об этом больше в другой раз), но это придуманный пример, не наседайте!И последнее изменение, я бы предложил также извлечь фильтрацию и отображение в функцию (для полного переиспользования кода).
И все вместе:
const isKitten = cat => cat.months < 7
const getName = cat => cat.name
const getKittenNames = cats =>
cats.filter(isKitten)
.map(getName)
const cats = [
{ name: 'Mojo', months: 84 },
{ name: 'Mao-Mao', months: 34 },
{ name: 'Waffles', months: 4 },
{ name: 'Pickles', months: 6 }
]
const kittens = getKittenNames(cats)
console.log(kittens)
Домашнее задание
Изучить извлечение методов filter и map из их объектов. Задание со звездочкой: исследовать композицию функций.
- Functional JavaScript: Decoupling methods from their objects
- Functional JavaScript: Function Composition For Every Day Use.
Что насчет break
Многие из вас спрашивали, “Что насчет
break
”, посмотрите часть вторую серии “Переосмысление JavaScript: break это GOTO циклов”.Заключение
Пишите что вы думаете по этому поводу в комментариях. Умер ли для вас цикл
for
?Для вас это мелочь, но меня очень радует когда кто-то подписывается на меня на медиуме или в твиттере (@joelnet), а если вы считаете мой рассказ бесполезным, то расскажите об этом в комментариях.
Спасибо!