Если вы занимаетесь изучением JavaScript, то вы, наверняка, сталкивались с понятием «функция высшего порядка» (Higher-Order Function). Может показаться, что это что-то очень сложное, но, на самом деле, это не так.
JavaScript подходит для функционального программирования благодаря тому, что он поддерживает концепцию функций высшего порядка. Такие функции широко используются в языке, и если вы программировали на JS, то вы, вероятно, уже с ними работали, даже не зная об этом.
Для того, чтобы в полной мере понять эту концепцию, вам сначала надо разобраться с понятием функционального программирования (Functional Programming) и с тем, что такое функции первого класса (First-Class Functions).
Материал, перевод которого мы публикуем, предназначен для начинающих, он направлен на объяснение концепции функций высшего порядка, и на демонстрацию того, как пользоваться ими в JavaScript.
Если описать концепцию функционального программирования простыми словами, то окажется, что это — подход к программированию, при использовании которого функции можно передавать другим функциям в качестве параметров и использовать функции в качестве значений, возвращаемых другими функциями. Занимаясь функциональным программированием, мы проектируем архитектуру приложения и пишем код с использованием функций.
Среди языков, поддерживающих функциональное программирование, можно отметить JavaScript, Haskell, Clojure, Scala и Erlang.
Если вы изучаете JavaScript, вы могли слышать, что в языке функции рассматриваются как объекты первого класса. Это так из-за того, что в JavaScript, как и в других языках, поддерживающих функциональное программирование, функции являются объектами.
В частности, в JS функции представлены в виде объектов особого типа — это объекты типа
Для того чтобы доказать, что функции в JavaScript являются объектами, мы можем сделать следующее, продолжая предыдущий пример:
Обратите внимание на то, что хотя добавление собственных свойств к стандартным объектам в JavaScript не вызывает сообщений об ошибках, делать так не рекомендуется. Не стоит добавлять собственные свойства к функциям. Если вам надо хранить что-то в объекте — лучше создайте для этого специальный объект.
В JavaScript с функциями можно делать то же самое, что можно делать с сущностями других типов, таких, как
Функции можно назначать переменным и константам:
Функции, назначенные переменным или константам, можно назначать другим переменным или константам:
Функции можно передавать в виде параметров для других функций:
Теперь, когда мы знаем о том, как ведут себя функции первого класса, поговорим о функциях высшего порядка.
Функции высшего порядка — это функции, которые работают с другими функциями, либо принимая их в виде параметров, либо возвращая их. Проще говоря, функцией высшего порядка называется такая функция, которая принимает функцию как аргумент или возвращает функцию в виде выходного значения.
Например, встроенные функции JavaScript
Рассмотрим примеры использования встроенных в JS функций высшего порядка и сравним такой подход с выполнением аналогичных действий без использования таких функций.
Метод
Функция обратного вызова, передаваемая
Предположим, у нас имеется массив чисел, и мы хотим создать новый массив, который содержит результаты умножения этих чисел на 2. Рассмотрим способы решения этой задачи с использованием функций высшего порядка и без них.
Объём этого кода можно даже сократить, если воспользоваться стрелочной функцией:
Предположим, у нас имеется массив, содержащий год рождения неких людей, и нам надо создать массив, в который попадёт их возраст в 2018 году. Рассмотрим, как и прежде, решение этой задачи в двух вариантах.
Метод
Рассмотрим пример, построенный по той же схеме, что и при рассмотрении метода
Предположим, у нас имеется массив, содержащий объекты, в свойствах которых хранятся сведения об имени и возрасте представителей некой группы людей. Нам надо создать массив, в котором будут сведения только о совершеннолетних представителях этой группы (тех, чей возраст достиг 18 лет).
Метод
Коллбэк принимает четыре параметра:
Если методу предоставлен параметр
Если параметр
Предположим, у нас есть массив чисел. Нам надо посчитать сумму его элементов.
Сначала рассмотрим использование метода
Каждый раз, когда коллбэк вызывается с передачей ему
Теперь посмотрим на то, как будет выглядеть решение задачи в том случае, если передать начальное значение в метод
Как видите, использование функции высшего порядка сделало наш код чище, лаконичнее и легче для восприятия.
До сих пор мы работали с функциями высшего порядка, встроенными в JS. Теперь давайте создадим нашу собственную функцию, работающую с другими функциями.
Представим, что в JavaScript нет стандартного метода массивов
Пусть у нас имеется массив строк, и мы хотели бы создать на его основе массив с числами, каждое из которых представляет собой длину строки, хранящейся в некоем элементе исходного массива.
В этом примере мы создали функцию высшего порядка
Коллбэк
В этом материале мы поговорили о функциях высшего порядка и исследовали некоторые встроенные функции JavaScript. Кроме того, мы разобрались с тем, как создавать собственные функции высшего порядка.
Если выразить в двух словах суть функций высшего порядка, то можно сказать, что это функции, которые могут принимать другие функции в качестве аргументов и возвращать другие функции в качестве результатов своей работы. Работа с другими функциями в функциях высшего порядка выглядит так же, как работа с любыми другими объектами.
Уважаемые читатели! Приходится ли вам писать собственные функции высшего порядка?
JavaScript подходит для функционального программирования благодаря тому, что он поддерживает концепцию функций высшего порядка. Такие функции широко используются в языке, и если вы программировали на JS, то вы, вероятно, уже с ними работали, даже не зная об этом.
Для того, чтобы в полной мере понять эту концепцию, вам сначала надо разобраться с понятием функционального программирования (Functional Programming) и с тем, что такое функции первого класса (First-Class Functions).
Материал, перевод которого мы публикуем, предназначен для начинающих, он направлен на объяснение концепции функций высшего порядка, и на демонстрацию того, как пользоваться ими в JavaScript.
Что такое функциональное программирование?
Если описать концепцию функционального программирования простыми словами, то окажется, что это — подход к программированию, при использовании которого функции можно передавать другим функциям в качестве параметров и использовать функции в качестве значений, возвращаемых другими функциями. Занимаясь функциональным программированием, мы проектируем архитектуру приложения и пишем код с использованием функций.
Среди языков, поддерживающих функциональное программирование, можно отметить JavaScript, Haskell, Clojure, Scala и Erlang.
Функции первого класса
Если вы изучаете JavaScript, вы могли слышать, что в языке функции рассматриваются как объекты первого класса. Это так из-за того, что в JavaScript, как и в других языках, поддерживающих функциональное программирование, функции являются объектами.
В частности, в JS функции представлены в виде объектов особого типа — это объекты типа
Function
. Рассмотрим пример:function greeting() {
console.log('Hello World');
}
// Вызов функции
greeting(); // выводит 'Hello World'
Для того чтобы доказать, что функции в JavaScript являются объектами, мы можем сделать следующее, продолжая предыдущий пример:
// К функции можно добавлять свойства, как и к любым другим объектам
greeting.lang = 'English';
// Выводит 'English'
console.log(greeting.lang);
Обратите внимание на то, что хотя добавление собственных свойств к стандартным объектам в JavaScript не вызывает сообщений об ошибках, делать так не рекомендуется. Не стоит добавлять собственные свойства к функциям. Если вам надо хранить что-то в объекте — лучше создайте для этого специальный объект.
В JavaScript с функциями можно делать то же самое, что можно делать с сущностями других типов, таких, как
Object
, String
, Number
. Функции можно передавать как параметры другим функциям. Такие функции, переданные другим, обычно выступают в роли функций обратного вызова (коллбэков). Функции можно назначать переменным, хранить их в массивах, и так далее. Именно поэтому функции в JS — это объекты первого класса.Назначение функций переменным и константам
Функции можно назначать переменным и константам:
const square = function(x) {
return x * x;
}
// выводит 25
square(5);
Функции, назначенные переменным или константам, можно назначать другим переменным или константам:
const foo = square;
// выводит 36
foo(6);
Передача функций в виде параметров
Функции можно передавать в виде параметров для других функций:
function formalGreeting() {
console.log("How are you?");
}
function casualGreeting() {
console.log("What's up?");
}
function greet(type, greetFormal, greetCasual) {
if(type === 'formal') {
greetFormal();
} else if(type === 'casual') {
greetCasual();
}
}
// выводит 'What's up?'
greet('casual', formalGreeting, casualGreeting);
Теперь, когда мы знаем о том, как ведут себя функции первого класса, поговорим о функциях высшего порядка.
Функции высшего порядка
Функции высшего порядка — это функции, которые работают с другими функциями, либо принимая их в виде параметров, либо возвращая их. Проще говоря, функцией высшего порядка называется такая функция, которая принимает функцию как аргумент или возвращает функцию в виде выходного значения.
Например, встроенные функции JavaScript
Array.prototype.map
, Array.prototype.filter
и Array.prototype.reduce
являются функциями высшего порядка.Функции высшего порядка в действии
Рассмотрим примеры использования встроенных в JS функций высшего порядка и сравним такой подход с выполнением аналогичных действий без использования таких функций.
▍Метод Array.prototype.map
Метод
map()
создаёт новый массив, вызывая, для обработки каждого элемента входного массива, коллбэк, переданный ему в виде аргумента. Этот метод берёт каждое возвращённое коллбэком значение и помещает его в выходной массив.Функция обратного вызова, передаваемая
map()
, принимает три аргумента: element
(элемент), index
(индекс) и array
(массив). Рассмотрим примеры.Пример №1
Предположим, у нас имеется массив чисел, и мы хотим создать новый массив, который содержит результаты умножения этих чисел на 2. Рассмотрим способы решения этой задачи с использованием функций высшего порядка и без них.
Решение задачи без использования функций высшего порядка
const arr1 = [1, 2, 3];
const arr2 = [];
for(let i = 0; i < arr1.length; i++) {
arr2.push(arr1[i] * 2);
}
// выводит [ 2, 4, 6 ]
console.log(arr2);
Решение задачи с помощью функции высшего порядка map
const arr1 = [1, 2, 3];
const arr2 = arr1.map(function(item) {
return item * 2;
});
console.log(arr2);
Объём этого кода можно даже сократить, если воспользоваться стрелочной функцией:
const arr1 = [1, 2, 3];
const arr2 = arr1.map(item => item * 2);
console.log(arr2);
Пример №2
Предположим, у нас имеется массив, содержащий год рождения неких людей, и нам надо создать массив, в который попадёт их возраст в 2018 году. Рассмотрим, как и прежде, решение этой задачи в двух вариантах.
Решение задачи без использования функций высшего порядка
const birthYear = [1975, 1997, 2002, 1995, 1985];
const ages = [];
for(let i = 0; i < birthYear.length; i++) {
let age = 2018 - birthYear[i];
ages.push(age);
}
// выводит [ 43, 21, 16, 23, 33 ]
console.log(ages);
Решение задачи с помощью функции высшего порядка map
const birthYear = [1975, 1997, 2002, 1995, 1985];
const ages = birthYear.map(year => 2018 - year);
// выводит [ 43, 21, 16, 23, 33 ]
console.log(ages);
▍Метод Array.prototype.filter
Метод
filter()
создаёт, на основе массива, новый массив, в которой попадают элементы исходного массива, соответствующие условию, заданному в переданной этому методу функции обратного вызова. Эта функция принимает, как и в случае с методом map()
, 3 аргумента: element
, index
и array
.Рассмотрим пример, построенный по той же схеме, что и при рассмотрении метода
map()
.Пример
Предположим, у нас имеется массив, содержащий объекты, в свойствах которых хранятся сведения об имени и возрасте представителей некой группы людей. Нам надо создать массив, в котором будут сведения только о совершеннолетних представителях этой группы (тех, чей возраст достиг 18 лет).
Решение задачи без использования функций высшего порядка
const persons = [
{ name: 'Peter', age: 16 },
{ name: 'Mark', age: 18 },
{ name: 'John', age: 27 },
{ name: 'Jane', age: 14 },
{ name: 'Tony', age: 24},
];
const fullAge = [];
for(let i = 0; i < persons.length; i++) {
if(persons[i].age >= 18) {
fullAge.push(persons[i]);
}
}
console.log(fullAge);
Решение задачи с помощью функции высшего порядка filter
const persons = [
{ name: 'Peter', age: 16 },
{ name: 'Mark', age: 18 },
{ name: 'John', age: 27 },
{ name: 'Jane', age: 14 },
{ name: 'Tony', age: 24},
];
const fullAge = persons.filter(person => person.age >= 18);
console.log(fullAge);
▍Метод Array.prototype.reduce
Метод
reduce()
обрабатывает каждый элемент массива с помощью коллбэка и помещает результат в единственное выходное значение. Этот метод принимает два параметра: коллбэк и необязательное начальное значение (initialValue
).Коллбэк принимает четыре параметра:
accumulator
(аккумулятор), currentValue
(текущее значение), currentIndex
(текущий индекс), sourceArray
(исходный массив).Если методу предоставлен параметр
initialValue
, то, в начале работы метода, accumulator
будет равен этому значению, а в currentValue
будет записан первый элемент обрабатываемого массива.Если параметр
initialValue
методу не предоставлен, то в accumulator
будет записан первый элемент массива, а в currentValue
— второй.Пример
Предположим, у нас есть массив чисел. Нам надо посчитать сумму его элементов.
Решение задачи без использования функций высшего порядка
const arr = [5, 7, 1, 8, 4];
let sum = 0;
for(let i = 0; i < arr.length; i++) {
sum = sum + arr[i];
}
// выводит 25
console.log(sum);
Решение задачи с помощью функции высшего порядка reduce
Сначала рассмотрим использование метода
reduce()
без предоставления ему начального значения.const arr = [5, 7, 1, 8, 4];
const sum = arr.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
});
// выводит 25
console.log(sum);
Каждый раз, когда коллбэк вызывается с передачей ему
currentValue
, то есть — очередного элемента массива, его параметр accumulator
оказывается содержащим результаты предыдущей операции, то есть того, что было возвращено из функции на предыдущей итерации. После завершения работы этого метода итоговый результат попадает в константу sum
.Теперь посмотрим на то, как будет выглядеть решение задачи в том случае, если передать начальное значение в метод
reduce()
.const arr = [5, 7, 1, 8, 4];
const sum = arr.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
}, 10);
// выводит 35
console.log(sum);
Как видите, использование функции высшего порядка сделало наш код чище, лаконичнее и легче для восприятия.
Создание собственных функций высшего порядка
До сих пор мы работали с функциями высшего порядка, встроенными в JS. Теперь давайте создадим нашу собственную функцию, работающую с другими функциями.
Представим, что в JavaScript нет стандартного метода массивов
map()
. Подобный метод мы вполне можем создать самостоятельно, что будет выражаться в разработке функции высшего порядка.Пусть у нас имеется массив строк, и мы хотели бы создать на его основе массив с числами, каждое из которых представляет собой длину строки, хранящейся в некоем элементе исходного массива.
const strArray = ['JavaScript', 'Python', 'PHP', 'Java', 'C'];
function mapForEach(arr, fn) {
const newArray = [];
for(let i = 0; i < arr.length; i++) {
newArray.push(
fn(arr[i])
);
}
return newArray;
}
const lenArray = mapForEach(strArray, function(item) {
return item.length;
});
// выводит [ 10, 6, 3, 4, 1 ]
console.log(lenArray);
В этом примере мы создали функцию высшего порядка
mapForEach
, которая принимает массив и функцию обратного вызова fn
. Функция mapForEach
проходится по массиву в цикле и вызывает коллбэк fn
на каждой итерации этого цикла. Коллбэк
fn
принимает текущий строковый элемент массива и возвращает длину этого элемента. То, что возвращает функция fn
, используется в команде newArray.push()
и попадает в массив, который возвратит функция mapForEach()
. Этот массив, в итоге, будет записан в константу lenArray
.Итоги
В этом материале мы поговорили о функциях высшего порядка и исследовали некоторые встроенные функции JavaScript. Кроме того, мы разобрались с тем, как создавать собственные функции высшего порядка.
Если выразить в двух словах суть функций высшего порядка, то можно сказать, что это функции, которые могут принимать другие функции в качестве аргументов и возвращать другие функции в качестве результатов своей работы. Работа с другими функциями в функциях высшего порядка выглядит так же, как работа с любыми другими объектами.
Уважаемые читатели! Приходится ли вам писать собственные функции высшего порядка?