[Перевод] Функции высшего порядка в JavaScript: что это такое?
Представляем вам перевод статьи Sukhjinder Arora, опубликованной на ресурсе Bits and Pieces. Узнайте под катом о функциях высшего порядка в JavaScript и о некоторых других функциях, встроенных в этот язык.
Фото NESA by Makers с сайта Unsplash
Обучаясь программированию на JavaScript, вы наверняка сталкивались с понятием функций высшего порядка. Хотя это понятие может показаться пугающим, на самом деле все не так сложно.
Именно благодаря способности работать с функциями высшего порядка, JavaScript подходит для функционального программирования.
Функции высшего порядка широко распространены в языке JavaScript. Если вы проработали с ним какое-то время, весьма вероятно, что вы использовали такие функции, даже не догадываясь об этом.
Чтобы получить полное представление о функциях высшего порядка, вам прежде всего нужно понять, что такое функциональное программирование и функции первого класса.
Совет: многократное применение функций JavaScript часто ведет к появлению дубликатов. Чтобы избежать этого, воспользуйтесь Bit (GitHub). Вы сможете с легкостью находить новые функции, делиться ими и применять их повторно с минимальными изменениями в управлении. Пробуйте, не стесняйтесь.
Что такое функциональное программирование
Если не вдаваться в подробности, функциональное программирование — это программирование, при котором одни функции передаются в качестве аргументов другим функциям и возвращают третьи функции в качестве значений. В функциональном программировании мы мыслим и оперируем функциями.
Функциональное программирование производится на таких языках, как JavaScript, Haskell, Clojure, Scala и Erlang.
Что такое функции первого класса
Обучаясь работе с JavaScript, вы, скорее всего, слышали, что для него функции — это граждане первого класса. Дело в том, что в JavaScript, как и в любом другом языке функционального программирования, функции являются объектами.
Конкретно для JavaScript функции — это объекты особого рода (объекты Function
, или функторы). Например:
function greeting() {
console.log('Hello World');
}
// Invoking the function
greeting(); // prints 'Hello World'
Чтобы показать, что функции в JavaScript являются объектами, мы можем проделать что-то вроде этого:
// We can add properties to functions like we do with objects
greeting.lang = 'English';
// Prints 'English'
console.log(greeting.lang);
Примечание: хотя вышеописанное и прекрасно работает в JavaScript, злоупотреблять подобным не стоит. Нельзя присваивать случайные свойства функторам — лучше используйте обычные объекты.
В JavaScript все, что вы можете проделать с другими сущностями, такими как объект, строка (string) или число, применимо и к функциям. Их можно передавать, в том числе в качестве аргументов другим функциям (тогда они называются callback-функциями или функциями обратного вызова), присваивать их переменным и так далее. Вот почему функции JavaScript называются функциями первого класса.
Присваивание функций переменным
JavaScript позволяет присваивать функции переменным. Например:
const square = function(x) {
return x * x;
}
// prints 25
square(5);
Также их можно передавать. Например:
const foo = square;
// prints 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();
}
}
// prints 'What's up?'
greet('casual', formalGreeting, casualGreeting);
Итак, теперь, зная, что такое функции первого класса, давайте обратимся к функциям высшего порядка в JavaScript.
Функции высшего порядка
Функции высшего порядка — это функции, которые работают с другими функциями, принимая функцию как аргумент или возвращая функцию как результат.
Примерами функций высшего порядка, которые уже встроены в язык, являются Array.prototype.map
, Array.prototype.filter
и Array.prototype.reduce
.
Функции высшего порядка в действии
Давайте рассмотрим несколько примеров встроенных функций высшего порядка и сравним их с решениями, при которых функции высшего порядка не используются.
Array.prototype.map
Метод map()
создает новый массив с результатом вызова передаваемой функции для каждого элемента начального массива. Метод map()
берет каждое значение callback-функции и создает новый массив, используя эти значения.
Callback-функция, передаваемая методу map()
, принимает три аргумента: element
, index
и array
.
Рассмотрим это на нескольких примерах.
Пример №1
Допустим, у нас есть массив чисел, и из него мы хотим создать новый массив, в котором каждое число из начального массива было бы удвоено. Как мы можем решить эту задачу при помощи функции высшего порядка и без нее?
Без функции высшего порядка:
const arr1 = [1, 2, 3];
const arr2 = [];
for(let i = 0; i < arr1.length; i++) {
arr2.push(arr1[i] * 2);
}
// prints [ 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
Скажем, у нас есть массив, содержащий годы рождения нескольких людей, и из него мы хотим создать новый массив, в котором будут их возрасты.
Без функции высшего порядка:
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);
}
// prints [ 43, 21, 16, 23, 33 ]
console.log(ages);
При помощи функции высшего порядка map
:
const birthYear = [1975, 1997, 2002, 1995, 1985];
const ages = birthYear.map(year => 2018 - year);
// prints [ 43, 21, 16, 23, 33 ]
console.log(ages);
Array.prototype.filter
Метод filter()
создает новый массив со всеми элементами, прошедшими проверку, задаваемую в передаваемой функции. Callback-функция, передаваемая методу filter()
, принимает три аргумента: element
, index
и array
.
Рассмотрим это на нескольких примерах.
Пример №1
Представим, что у нас есть массив, содержащий объекты со свойствами «имя» и «возраст». Из него мы хотим создать новый массив, в котором будут указаны только совершеннолетние (от восемнадцати и старше).
Без функции высшего порядка:
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
применяет функцию к каждому значению массива, сводя его к одному значению. Метод reduce принимает два аргумента:
- callback-функцию, которую требуется обработать;
- необязательный параметр
initialValue
(первый аргумент при первом вызове функции).
Callback-функция принимает четыре аргумента: accumulator
, currentValue
, currentIndex
, sourceArray
.
Если параметр initialValue
был передан, аргумент accumulator
будет равен аргументу initialValue
, а аргумент currentValue
— первому элементу массива.
Если параметр initialValue
передан не был, аргумент accumulator
будет равен первому элементу массива, а в качестве аргумента currentValue
будет взят второй элемент массива.
Пример №1
Предположим, нам нужно найти сумму чисел массива.
При помощи функции высшего порядка reduce
:
const arr = [5, 7, 1, 8, 4];
const sum = arr.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
});
// prints 25
console.log(sum);
Каждый раз, когда callback-функция применяется к каждому значению из массива, аргумент accumulator
сохраняет результат предыдущего действия, возвращенный функцией, а currentValue
принимает следующее значение массива. По завершении работы результат сохраняется в переменной sum
.
Кроме того, мы можем передать этой функции начальное значение:
const arr = [5, 7, 1, 8, 4];
const sum = arr.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
}, 10);
// prints 35
console.log(sum);
Без функции высшего порядка:
const arr = [5, 7, 1, 8, 4];
let sum = 0;
for(let i = 0; i < arr.length; i++) {
sum = sum + arr[i];
}
// prints 25
console.log(sum);
Как видите, при помощи функции высшего порядка код можно сделать аккуратнее, короче и емче.
Создание собственной функции высшего порядка
До этого момента мы рассматривали различные функции высшего порядка, встроенные в язык. Пора создать собственную функцию высшего порядка.
Представим, что в 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;
});
// prints [ 10, 6, 3, 4, 1 ]
console.log(lenArray);
В приведенном примере мы создали функцию высшего порядка mapForEach
, которая принимает в качестве аргументов массив и callback-функцию fn
. Эта функция циклично применяется к каждому элементу массива и вызывает callback-функцию fn
в рамках вызова функции newArray.push
в каждой итерации.
Callback-функция fn
принимает текущий элемент начального массива и возвращает значение длины этого элемента, которое сохраняется внутри нового массива newArray
. После завершения итерации массив newArray
возвращается в качестве результата и назначается массивом lenArray
.
Заключение
Мы узнали, что такое функции высшего порядка, и рассмотрели некоторые встроенные в язык функции. Также мы узнали, как создавать собственные функции высшего порядка.
Если вкратце, функции высшего порядка работают как обычные функции, но обладают дополнительной способностью получать другие функции как аргумент и возвращать их как результат.
Вот, собственно, и все. Если данная статья показалась вам полезной, вы можете также подписаться на меня на Medium и в Twitter. Не стесняйтесь — комментируйте, если у вас есть вопросы! Я буду рад помочь. :)