Предлагаем вашему вниманию переводной материал об использовании 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() в вашем приложении.
