Frontender Magazine

Хватит писать циклы for. Пришло время использовать Underscore.

мальчик

Я НЕ БУДУ есть ветчину и яичницу!

Сколько циклов for вы написали сегодня? А за эту неделю?

var i;

for(i = 0; i < someArray.length; i++) {
  var someThing = someArray[i];
  doSomeWorkOn(someThing);
}

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

var i,
  j;

for(i = 0; i < someArray.length; i++) {
  var someThing = someArray[i];
  for(j = 0; j < someThing.stuff.length; j++) {
      doSomeWorkOn(someThing.stuff[j]);
  }
}

По шкале качества, этот пример не так уж плох, но если добавить туда ещё парочку if, начинается полное безумие.

За два года я не написал ни одного цикла for.

«О чем, черт побери, ты говоришь?»

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

Как мне это удалось?

_.each(someArray, function(someThing) {
  doSomeWorkOn(someThing);
})

Или, даже проще:

_.each(someArray, doSomeWorkOn); //спасибо, paulmcpazzi!

Это библиотека Underscore.js в действии. Она порождает чистый, простой для чтения, емкий код, без обилия переменных и эшелонов точек с запятыми. Все предельно ясно и чётко.

Вот ещё пример:

var i,
  result = [];

for(i = 0; i < someArray.length; i++) {
  var someThing = someArray[i];
  // между тем, у меня уже руки отваливаются столько кода набирать
  if(someThing.isAwesome === true) {
      result.push(someArray[i]);
  }
}

Опять же, типичный случай применения проверенных временем циклов for. Мда. Как это случается с экс-курильщиками и новообращенными веганами, даже одно упоминание о подобном коде наполняет меня праведным негодованием.

var result = _.filter(someArray, function(someThing) {
  return someThing.isAwesome === true;
})

Как и предполагает имя метода filter из Underscore.js, эти три строчки простого для парсинга кода вернули мне новый массив из всяких клёвых штук.

Или, может мне нужно выполнить определённые действия с элементами массива и получить новый, содержащий результаты этих действий?

var result = _.map(someArray, function(someThing) {
  return trasformTheThing(someThing);
})

Эти три функции безумно полезны в ежедневной работе и затрагивают только малую часть возможностей, которые открывает библиотека Underscore.js.

var grandTotal = 0,
  somePercentage = 1.07,
  severalNumbers = [33, 54, 42],
  i; // не забудьте увеличить индекс;

for(i = 0; i < severalNumbers.length; i++) {
  var aNumber = severalNumbers[i];
  grandTotal += aNumber * somePercentage;
}

Жесть.

var somePercentage = 1.07,
  severalNumbers = [33, 54, 42],
  grandTotal;

grandTotal = _.reduce(severalNumbers, function(runningTotal, aNumber) {
  return runningTotal + (aNumber * somePercentage);
}, 0)

Сначала, этот подход казался мне странным, и я всё ещё заглядываю в документацию, когда речь идёт о методах, вроде reduce. Однако, простая осведомлённость об их существовании и категорический отказ от использования циклов for — мое основное подспорье в борьбе за качество кода. Методы, представленные выше — это лишь малая толика. Библиотека Underscore.js наполнена потрясающими средствами подобного рода, которые можно комбинировать для получения новых, поистине чудесных возможностей.

Испытание: 30 дней без циклов

Стоп.

Следующие 30 дней вы должны отказаться от циклов for. Если увидите кучку этой гадости, замените её на each или map. Сокращайте по чуть-чуть код, тут и там. И держите меня в курсе о ваших успехах.

Но, будьте осторожны. Underscore.js — это врата в функциональное программирование. Однажды попробовав, вы уже не сможете отказаться. В хорошем смысле.

Если хотите копнуть глубже, ознакомьтесь с этим руководством по функциональному программированию в JavaScript. Оно действительно очень стоящее, и займет у вас около получаса. Это описание основных принципов работы функций Underscore.js, которые я использовал выше, нечто вроде «настольной поваренной книги». Кладезь веселья для гиков.

Примечание

В качестве более производительной альтернативы Underscore.js, можете ознакомиться с lodash.

Стоит также отметить, что у современных браузеров присутствует встроенная поддержка описанных выше методов. Вполне имеют право на жизнь Array.forEach, Array.reduce и Array.map, однако для них вам, вероятно, потребуется создать фолбэк, на случай отсутствия поддержки. Для меня намного более удобным является стабильный API Underscore.js (lodash). Но вы вправе с этим не соглашаться.

Конечно же, с циклами for код исполняется быстрее. Перед тем, как оптимизировать производительность для сжатия и последующей обработки центральным процессором, я работаю над простотой восприятия кода для моей команды. Я не пишу игры или сложные, анимированные пользовательские интерфейсы. Большие проекты, десятки разработчиков - код, который и без того стремится к разрастанию и беспорядку.

В данном случае, оптимизация с целью получить чистый, удобочитаемый код приносит больший результат, даже если он даётся ценой весьма несущественной потери в производительности.

Теперь, готовя большой список элементов в Angular, мы сосредотачиваем внимание на производительности с точки зрения центрального процессора. Но, даже так, единственная ситуация, когда мы реально зашли в тупик — это из-за не оптимизированной сетки с данными, случившаяся для телефонов на базе ОС Android прошлого поколения.

Чистота превыше всего! ;)

Если вы заметили ошибку, вы всегда можете отредактировать статью, создать issue или просто написать об этом Антону Немцеву в skype ravencry.

Joel Hooks
Автор:
Joel Hooks
Twitter:
@jhooks
Сaйт:
joelhooks.com/
Google Plus:
JoelHooks
Наталья Фадеева
Переводчик:
Наталья Фадеева
вКонтакте:
natatik_l
Twitter:
@very_busy_girl
GitHub:
NatalieF

Комментарии (18 комментариев, если быть точным)

Автар пользователя
ai

КофеСкрипт решает проблему ещё лучше — синтаксис for короче, чем у Underscore и Array#forEach, а скорость выше.

Автар пользователя
iamstarkov

.forEach доступен уже сейчас в современных браузерах, а в ноде плюсом к нему доступны ещё и замечательные .map, .reduce

Автар пользователя
lahmatiy

Советовать не использовать циклы – это из серии вредных советов. У всего свое предназначение. Отказываться от чего-то из-за абстрактной красоты и притянутых за уши аргументов вроде "больше кода" очень глупо.
Методы forEach, map, filter, some, every, reduce, reduceRight уже давно есть в любом браузере: IE9+, Opera 10.1+ (reduce/reduceRight с 10.5), Firefox 1.5+, Chrome, Safari 3.2+ (reduce/reduceRight с 4.0).
В основном полифил нужен для IE8 и младше, и он явно не больше чем реализация этих методов в underscore.

Автар пользователя
reklatsmasters

Какая-то упоротая статья. Линковать целую либу ради циклов? Уж лучше подключить ES5 shim и юзать стандартные функции массива.

Автар пользователя
iamstarkov

@ReklatsMasters

или

Линковать либу ради циклов?…

или

…лучше подключить ES5 shim

а в чём принципиальная разница?

Автар пользователя
reklatsmasters

а в чём принципиальная разница?

Мы можем shim подключать только для осла.

Автар пользователя
sadcitizen

Зашёл в комментарии, чтобы прочитать про coffee и нативные методы.

Если вы посмотрите исходники underscore, то увидите, что _.each, _.map и прочие, это просто полифилы. И сама эта инструментальная библиотека вкусна как раз не этими методами.

Автар пользователя
h4

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

Автар пользователя
onmoon

Исходя из того, и в undescore все методы (_.each и т.д.) используют тот же самый цикл (var i = 0, length = obj.length; i < length; i++) можно с уверенность говорить, что данная статья бесполезна. Например я использую undescore для более читаемого синтаксиса и удобства работы для backbone приложений, но подключать везде где только можно нет смысла и зависит от контекста выполняемой задачи. Насчет производительности можно сказать , что for (var i = obj.length; i--;) работает быстрее чем (var i = 0, length = obj.length; i < length; i++) и пишется короче, но его можно применять не везде.

Автар пользователя
DarkXaHTeP

Стоит учитывать что в случае с foreach, each и прочими нет возможности сделать break цикла, что иногда так же необходимо.

Автар пользователя
ai

В библиотеках можно сделать return false, но тогда не сделать выход из вышестоящей функции.

Так что КофеСкрипт тут тоже лучше — в его цикл и удобнее и позволяет использовать break и continue.

Автар пользователя
lolmaus

Две лучшие вещи, которые произошли со мной при освоении JavaScript, это CoffeeScript и SugarJS. Всем горячо рекомендую.

Автар пользователя
h4

@onmoon underscore использует цикл только в том случае, если в браузере не имплементирован Array.prototype.foreEach. http://underscorejs.org/docs/underscore.html#section-15

Автар пользователя
rageofaxe

И все это называется list comprehension или абстракция списка. Нормальные люди только так и пишут.

Автар пользователя
vyushin

Хорошая статья. Стоит учитывать, что в ней раскрыты не все возможности underscore.js Но это не проблема. Кому интересно "копнуть" глубже - тот найдет в сети сайт http://underscorejs.ru/

Автар пользователя
Wejur

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

Автар пользователя
dhawkmoon

var i, result = [];

for(i = 0; i < someArray.length; i++) { var someThing = someArray[i]; // между тем, у меня уже руки отваливаются столько кода набирать if(someThing.isAwesome === true) { result.push(someArray[i]); } }

для этого примера еще есть Array.filter()

Автар пользователя
lolmaus

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

"Не ешьте сырое мясо, ешьте котлеты" -- очень забавно, ведь в приготовлении котлет используется сырое мясо. Котлеты -- логическая бессмыслица!