Предлагаем вашему вниманию переводной материал об использовании map и reduce в функциональном JavaScript. Эта статья будет интересна в первую очередь начинающим разработчикам.
За всеми этими разговорами о новых стандартах легко забыть о том, что именно ECMAScript 5 подарил нам ряд инструментов, благодаря которым мы сегодня можем использовать функциональное программирование в JavaScript. Например, нативные методы map() и reduce() на базе JS-объекта
Безусловно, читабельность и обслуживаемость кода не должны снижать производительность, если этого требует ситуация. Современные браузеры эффективнее выполняют более громоздкие традиционные конструкции, например, циклы.
Попробуйте следующую методику: сначала напишите код, исходя из критериев читабельности и обслуживаемости, а затем оптимизируйте его производительность, если в этом есть реальная потребность. Избегайте преждевременной оптимизации.
Также стоит отметить, что применение методов наподобие
Маппинг — фундаментальная методика в функциональном программировании. Она применяется для оперирования всеми элементами массива с целью создания другого массива той же длины, но с преобразованным содержимым.
Чтобы было понятнее, давайте рассмотрим простой пример. Допустим, у вас есть массив слов, и вам нужно преобразовать его в массив, содержащий длины всех слов исходного массива. Да, это не самый актуальный случай, но понимание того, как работает этот инструмент, поможет вам применять его в дальнейшем для улучшения своего кода.
Вероятно, вы уже знаете, как выполнить описанную задачу с помощью цикла
Здесь определено несколько переменных:
Далее мы итерируем каждый элемент массива
Примечание: эту задачу можно было бы решить лаконичнее, без переменной элемента и промежуточного присваивания, передавая длину
Технически это правильный подход. Он будет работать на любом стандартном JS-движке. Но когда вы узнаете про
Вот как можно решить нашу задачу с помощью
Здесь мы опять начинаем с переменной для массива
Обратите внимание, что при таком подходе:
Ещё одно преимущество этого подхода заключается в том, что мы можем сделать его гибче, разделив именованную функцию. При этом код станет чище. Анонимные встраиваемые функции затрудняют повторное использование кода и могут выглядеть неопрятно. Можно было бы определить именованную функцию
Посмотрите, насколько чище стал выглядеть код. Просто начните применять маппинг, и сразу выйдете на новый уровень функционального программирования.
Любопытно, что при добавлении маппинга в объект массива, ECMAScript 5 превращает основной тип массива в полный функтор. Это делает функциональное программирование ещё более доступным.
Согласно классическим определениям функционального программирования, функтор удовлетворяет трём критериям:
Если хотите больше узнать о функторах, можете посмотреть видео Маттиаса Питера Йоханссона.
Метод reduce() впервые появился в ECMAScript 5. Он аналогичен
После описания начального массива, мы создаём переменную
Опять же, чисто технически здесь всё в порядке. Обработали массив, получили результат. Но с помощью метода
Здесь мы определяем новую переменную
Обратите внимание, что мы обнуляем второй аргумент
Возможно, описанный подход выглядит слишком сложным. Это следствие интегрированного определения встраиваемой функции в вызываемом методе
Получается немного длиннее, но это не всегда недостаток. В данном случае становится понятнее, что происходит с методом
Привыкнув регулярно использовать
Помимо
За всеми этими разговорами о новых стандартах легко забыть о том, что именно ECMAScript 5 подарил нам ряд инструментов, благодаря которым мы сегодня можем использовать функциональное программирование в JavaScript. Например, нативные методы map() и reduce() на базе JS-объекта
Array
. Если вы до сих пор не пользуетесь map()
и reduce()
, то сейчас самое время начать. Наиболее современные JS-платформы нативно поддерживают ECMAScript 5. Использование этих методов позволит сделать ваш код гораздо чище, читабельнее и удобнее в обслуживании. map()
и reduce()
помогут вам встать на путь более элегантной функциональной разработки. Замечание по производительности
Безусловно, читабельность и обслуживаемость кода не должны снижать производительность, если этого требует ситуация. Современные браузеры эффективнее выполняют более громоздкие традиционные конструкции, например, циклы.
Попробуйте следующую методику: сначала напишите код, исходя из критериев читабельности и обслуживаемости, а затем оптимизируйте его производительность, если в этом есть реальная потребность. Избегайте преждевременной оптимизации.
Также стоит отметить, что применение методов наподобие
map()
и reduce()
позволит извлечь больше преимуществ из улучшений JS-движка, по мере того, как браузеры будут оптимизироваться для их использования. Если у вас нет проблем с производительностью, то лучше писать код с оптимистическим расчётом на будущее. А приёмы повышения производительности, делающие код менее опрятным, оставьте на потом, когда в них возникнет потребность.Использование map
Маппинг — фундаментальная методика в функциональном программировании. Она применяется для оперирования всеми элементами массива с целью создания другого массива той же длины, но с преобразованным содержимым.
Чтобы было понятнее, давайте рассмотрим простой пример. Допустим, у вас есть массив слов, и вам нужно преобразовать его в массив, содержащий длины всех слов исходного массива. Да, это не самый актуальный случай, но понимание того, как работает этот инструмент, поможет вам применять его в дальнейшем для улучшения своего кода.
Вероятно, вы уже знаете, как выполнить описанную задачу с помощью цикла
for
. Например, так:var animals = ["cat","dog","fish"];
var lengths = [];
var item;
var count;
var loops = animals.length;
for (count = 0; count < loops; count++){
item = animals[count];
lengths.push(item.length);
}
console.log(lengths); //[3, 3, 4]
Здесь определено несколько переменных:
- массив
animals
содержит исходные слова, - пустой массив
lengths
будет содержать результаты выполнения операции, - переменная
item
используется для временного хранения каждого из элементов массива, которым мы манипулируем во время выполнения каждого цикла, - массив
for
содержит внутреннюю переменнуюcount
и оптимизирован с помощью переменной loops.
Далее мы итерируем каждый элемент массива
animals
: вычисляем длину и помещаем в массив lengths
.Примечание: эту задачу можно было бы решить лаконичнее, без переменной элемента и промежуточного присваивания, передавая длину
animals[count]
напрямую в массив lengths
. Код стал бы немного короче, но и менее читабельным даже в этом простом примере. Аналогично, чтобы слегка повысить производительность, можно было бы использовать известную длину массива animals
для инициализации массива lengths
как new Array(animals.length)
, а затем вместо применения push
внести элементы по индексу. Но это тоже сделало бы код немного менее понятным. В общем, всё зависит от того, как вы будете использовать свой код в реальных проектах.Технически это правильный подход. Он будет работать на любом стандартном JS-движке. Но когда вы узнаете про
map()
, то классический способ сразу покажется слишком громоздким.Вот как можно решить нашу задачу с помощью
map()
:var animals = ["cat","dog","fish"];
var lengths = animals.map(function(animal) {
return animal.length;
});
console.log(lengths); //[3, 3, 4]
Здесь мы опять начинаем с переменной для массива
animals
. Но кроме неё мы объявляем только lengths
, и напрямую присваиваем ей результат, полученный при маппинге анонимной встраиваемой функции в каждый элемент массива animals
. Анонимная функция выполняет операцию по каждому животному и возвращает длину. В конце концов массив lengths
, содержащий длины каждого слова, становится такой же длины, как и исходный animals
.Обратите внимание, что при таком подходе:
- Код получается гораздо короче.
- Нужно объявлять гораздо меньше переменных. Следовательно, мы создаём меньше шума в глобальном пространстве имён, снижая вероятность возникновения коллизий, если другая часть того же кода использует переменные с теми же именами.
- Ни одной переменной не нужно менять своё значение от начала и до конца цикла. По мере изучения функционального программирования вы будете всё больше ценить изящную силу использования констант и неизменяемых переменных. И начинать никогда не поздно.
Ещё одно преимущество этого подхода заключается в том, что мы можем сделать его гибче, разделив именованную функцию. При этом код станет чище. Анонимные встраиваемые функции затрудняют повторное использование кода и могут выглядеть неопрятно. Можно было бы определить именованную функцию
getLength()
и использовать её следующим образом: var animals = ["cat","dog","fish"];
function getLength(word) {
return word.length;
}
console.log(animals.map(getLength)); //[3, 3, 4]
Посмотрите, насколько чище стал выглядеть код. Просто начните применять маппинг, и сразу выйдете на новый уровень функционального программирования.
Что такое функтор?
Любопытно, что при добавлении маппинга в объект массива, ECMAScript 5 превращает основной тип массива в полный функтор. Это делает функциональное программирование ещё более доступным.
Согласно классическим определениям функционального программирования, функтор удовлетворяет трём критериям:
- Содержит набор значений.
- Реализует функцию
map
для оперирования каждым элементом. - Функция
map
возвращает функтор того же размера.
Если хотите больше узнать о функторах, можете посмотреть видео Маттиаса Питера Йоханссона.
Использование reduce
Метод reduce() впервые появился в ECMAScript 5. Он аналогичен
map()
, за исключением того, что вместо создания другого функтора reduce()
производит единичный результат, который может быть любого типа. Например, вам нужно получить в виде числа сумму длин всех слов в массиве animals
. Вероятно, вы сразу напишете примерно так:var animals = ["cat","dog","fish"];
var total = 0;
var item;
for (var count = 0, loops = animals.length; count < loops; count++){
item = animals[count];
total += item.length;
}
console.log(total); //10
После описания начального массива, мы создаём переменную
total
для подсчёта суммы, и присваиваем ей ноль. Также создаём переменную item
, в которой, по мере выполнения цикла for
, сохраняется результат каждой итерации над массивом animals
. В качестве счётчика циклов используется переменная count
, а loops
применяется для оптимизации итераций. Запускаем цикл for
, итерируем все слова в массиве animals
, присваивая значение каждого из них переменной item
, и прибавляем длины слов к нарастающему итогу.Опять же, чисто технически здесь всё в порядке. Обработали массив, получили результат. Но с помощью метода
reduce()
можно это сделать гораздо проще:var animals = ["cat","dog","fish"];
var total = animals.reduce(function(sum, word) {
return sum + word.length;
}, 0);
console.log(total);
Здесь мы определяем новую переменную
total
и присваиваем ей значение результата, полученного после применения reduce
к массиву animals
с использованием двух параметров: анонимной встраиваемой функции и нарастающего итога. reduce
берёт каждый элемент массива, применяет к нему функцию и добавляет получаемый результат к нарастающему итогу, которая затем передаётся в следующую итерацию. Подставляемая функция получает два параметра: нарастающий итог и текущее обрабатываемое слово из массива. К длине этого слова функция добавляет текущее значение total
.Обратите внимание, что мы обнуляем второй аргумент
reduce()
, значит total
является числом. Метод reduce()
будет работать и без второго аргумента, но результат может отличаться от ожидаемого. Попробуйте сами определить, какую логику использует JavaScript при исключении total
.Возможно, описанный подход выглядит слишком сложным. Это следствие интегрированного определения встраиваемой функции в вызываемом методе
reduce()
. Давайте зададим именованную функцию вместо анонимной встраиваемой:var animals = ["cat","dog","fish"];
var addLength = function(sum, word) {
return sum + word.length;
};
var total = animals.reduce(addLength, 0);
console.log(total);
Получается немного длиннее, но это не всегда недостаток. В данном случае становится понятнее, что происходит с методом
reduce()
. Он получает два параметра: функцию, которая применяется к каждому элементу массива, и начальное значение нарастающего итога. В данном случае мы передаём имя новой функции addLength
начальное значение (нулевое) нарастающего итога. Функция addLength()
также получает два параметра: нарастающий итог и строковое значение.Заключение
Привыкнув регулярно использовать
map()
и reduce()
, вы сможете сделать свой код чище, гибче и легче в обслуживании. Это откроет вам дорогу к использованию других функциональных подходов в JavaScript.Помимо
map()
и reduce()
в ECMAScript 5 появились и другие новые методы. Вероятно, улучшение качества кода и удовольствие от разработки, которое вы прочувствуете, намного перевесят временное ухудшение производительности. Используйте функциональные подходы и измеряйте влияние на производительность в реальных проектах, вместо того, чтобы думать, а нужны ли map()
и reduce()
в вашем приложении.