Если вы занимаетесь изучением JavaScript, то вы, наверняка, сталкивались с понятием «функция высшего порядка» (Higher-Order Function). Может показаться, что это что-то очень сложное, но, на самом деле, это не так.
JavaScript подходит для функционального программирования благодаря тому, что он поддерживает концепцию функций высшего порядка. Такие функции широко используются в языке, и если вы программировали на JS, то вы, вероятно, уже с ними работали, даже не зная об этом.
Для того, чтобы в полной мере понять эту концепцию, вам сначала надо разобраться с понятием функционального программирования (Functional Programming) и с тем, что такое функции первого класса (First-Class Functions).
Материал предназначен для начинающих, он направлен на объяснение концепции функций высшего порядка, и на демонстрацию того, как пользоваться ими в JavaScript.
Что такое функциональное программирование?
Если описать концепцию функционального программирования простыми словами, то окажется, что это — подход к программированию, при использовании которого функции можно передавать другим функциям в качестве параметров и использовать функции в качестве значений, возвращаемых другими функциями. Занимаясь функциональным программированием, мы проектируем архитектуру приложения и пишем код с использованием функций.
Среди языков, поддерживающих функциональное программирование, можно отметить JavaScript, Haskell, Clojure, Scala и Erlang.
Функции первого класса
Если вы изучаете JavaScript, вы могли слышать, что в языке функции рассматриваются как объекты первого класса. Это так из-за того, что в JavaScript, как и в других языках, поддерживающих функциональное программирование, функции являются объектами.
В частности, в JS функции представлены в виде объектов особого типа — это объекты типа Function
. Рассмотрим пример:
Для того чтобы доказать, что функции в JavaScript являются объектами, мы можем сделать следующее, продолжая предыдущий пример:
Обратите внимание на то, что хотя добавление собственных свойств к стандартным объектам в JavaScript не вызывает сообщений об ошибках, делать так не рекомендуется. Не стоит добавлять собственные свойства к функциям. Если вам надо хранить что-то в объекте — лучше создайте для этого специальный объект.
В JavaScript с функциями можно делать то же самое, что можно делать с сущностями других типов, таких, как Object
, String
, Number
. Функции можно передавать как параметры другим функциям. Такие функции, переданные другим, обычно выступают в роли функций обратного вызова (коллбэков). Функции можно назначать переменным, хранить их в массивах, и так далее. Именно поэтому функции в JS — это объекты первого класса.
Назначение функций переменным и константам
Функции можно назначать переменным и константам:
Функции, назначенные переменным или константам, можно назначать другим переменным или константам:
Передача функций в виде параметров
Функции можно передавать в виде параметров для других функций:
Теперь, когда мы знаем о том, как ведут себя функции первого класса, поговорим о функциях высшего порядка.
Функции высшего порядка
Функции высшего порядка — это функции, которые работают с другими функциями, либо принимая их в виде параметров, либо возвращая их. Проще говоря, функцией высшего порядка называется такая функция, которая принимает функцию как аргумент или возвращает функцию в виде выходного значения.
Например, встроенные функции JavaScript Array.prototype.map
, Array.prototype.filter
и Array.prototype.reduce
являются функциями высшего порядка.
Функции высшего порядка в действии
Рассмотрим примеры использования встроенных в JS функций высшего порядка и сравним такой подход с выполнением аналогичных действий без использования таких функций.
Метод Array.prototype.map
Метод map()
создаёт новый массив, вызывая, для обработки каждого элемента входного массива, коллбэк, переданный ему в виде аргумента. Этот метод берёт каждое возвращённое коллбэком значение и помещает его в выходной массив.
Функция обратного вызова, передаваемая map()
, принимает три аргумента: element
(элемент), index
(индекс) и array
(массив). Рассмотрим примеры.
Пример №1
Предположим, у нас имеется массив чисел, и мы хотим создать новый массив, который содержит результаты умножения этих чисел на 2. Рассмотрим способы решения этой задачи с использованием функций высшего порядка и без них.
Решение задачи без использования функций высшего порядка
Решение задачи с помощью функции высшего порядка map
Объём этого кода можно даже сократить, если воспользоваться стрелочной функцией:
Пример №2
Предположим, у нас имеется массив, содержащий год рождения неких людей, и нам надо создать массив, в который попадёт их возраст в 2018 году. Рассмотрим, как и прежде, решение этой задачи в двух вариантах.
Решение задачи без использования функций высшего порядка
Решение задачи с помощью функции высшего порядка map
Метод Array.prototype.filter
Метод filter()
создаёт, на основе массива, новый массив, в которой попадают элементы исходного массива, соответствующие условию, заданному в переданной этому методу функции обратного вызова. Эта функция принимает, как и в случае с методом map()
, 3 аргумента: element
, index
и array
.
Рассмотрим пример, построенный по той же схеме, что и при рассмотрении метода map()
.
Пример
Предположим, у нас имеется массив, содержащий объекты, в свойствах которых хранятся сведения об имени и возрасте представителей некой группы людей. Нам надо создать массив, в котором будут сведения только о совершеннолетних представителях этой группы (тех, чей возраст достиг 18 лет).
Решение задачи без использования функций высшего порядка
Решение задачи с помощью функции высшего порядка filter
Метод Array.prototype.reduce
Метод reduce()
обрабатывает каждый элемент массива с помощью коллбэка и помещает результат в единственное выходное значение. Этот метод принимает два параметра: коллбэк и необязательное начальное значение (initialValue
).
Коллбэк принимает четыре параметра: accumulator
(аккумулятор), currentValue
(текущее значение), currentIndex
(текущий индекс), sourceArray
(исходный массив).
Если методу предоставлен параметр initialValue
, то, в начале работы метода, accumulator
будет равен этому значению, а в currentValue
будет записан первый элемент обрабатываемого массива.
Если параметр initialValue
методу не предоставлен, то в accumulator
будет записан первый элемент массива, а в currentValue
— второй.
Пример
Предположим, у нас есть массив чисел. Нам надо посчитать сумму его элементов.
Решение задачи без использования функций высшего порядка
Решение задачи с помощью функции высшего порядка reduce
Сначала рассмотрим использование метода reduce()
без предоставления ему начального значения.
Каждый раз, когда коллбэк вызывается с передачей ему currentValue
, то есть — очередного элемента массива, его параметр accumulator
оказывается содержащим результаты предыдущей операции, то есть того, что было возвращено из функции на предыдущей итерации. После завершения работы этого метода итоговый результат попадает в константу sum
.
Теперь посмотрим на то, как будет выглядеть решение задачи в том случае, если передать начальное значение в метод reduce()
.
Как видите, использование функции высшего порядка сделало наш код чище, лаконичнее и легче для восприятия.
Создание собственных функций высшего порядка
До сих пор мы работали с функциями высшего порядка, встроенными в JS. Теперь давайте создадим нашу собственную функцию, работающую с другими функциями.
Представим, что в JavaScript нет стандартного метода массивов map()
. Подобный метод мы вполне можем создать самостоятельно, что будет выражаться в разработке функции высшего порядка.
Пусть у нас имеется массив строк, и мы хотели бы создать на его основе массив с числами, каждое из которых представляет собой длину строки, хранящейся в некоем элементе исходного массива.
В этом примере мы создали функцию высшего порядка mapForEach
, которая принимает массив и функцию обратного вызова fn
. Функция mapForEach
проходится по массиву в цикле и вызывает коллбэк fn
на каждой итерации этого цикла.
Коллбэк fn
принимает текущий строковый элемент массива и возвращает длину этого элемента. То, что возвращает функция fn
, используется в команде newArray.push()
и попадает в массив, который возвратит функция mapForEach()
. Этот массив, в итоге, будет записан в константу lenArray
.
Итоги
В этом материале мы поговорили о функциях высшего порядка и исследовали некоторые встроенные функции JavaScript. Кроме того, мы разобрались с тем, как создавать собственные функции высшего порядка.
Если выразить в двух словах суть функций высшего порядка, то можно сказать, что это функции, которые могут принимать другие функции в качестве аргументов и возвращать другие функции в качестве результатов своей работы. Работа с другими функциями в функциях высшего порядка выглядит так же, как работа с любыми другими объектами.
Перевод статьи Understanding Higher-Order Functions in JavaScript