Вы узнаете, что такое функции высшего порядка и как их применять в JavaScript.
Если вы изучаете JavaScript, то наверняка сталкивались с термином ― «Функции высшего порядка». Звучит так, как будто это нечто сложное, но это не так.
Функции высшего порядка позволяют JavaScript быть пригодным для функционального программирования.
Такие функции широко используются в JavaScript. Если вы хоть немного программировали на JavaScript, то наверняка использовали их, возможно, даже не догадываясь об этом.
Чтобы в полной мере понять эту концепцию, вам следует сначала разобраться с функциональным программированием и концепцией функций первого класса.
Что такое функциональное программирование
Если говорить простыми словами, то функциональное программирование — это способ программировать, при котором вы можете передавать функции в качестве параметров другим функциям, а также возвращать их как значения. В функциональном программировании мы думаем и пишем код с точки зрения функций.
JavaScript, Haskell, Clojure, Scala, и Erlang — это лишь некоторые языки, в которых применяется такой способ программирования.
Функции первого класса
Если вы уже изучаете JavaScript, то могли слышать, что 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 называют — «Функции первого класса».
Назначение функций переменным
В 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);
Теперь мы знаем, что такое функции первого класса. Можно приступать к «Функциям высшего порядка».
Функции высшего порядка
Такие функции оперируют другими функциями, принимая их в качестве аргументов или возвращая их. Проще говоря, функции высшего порядка ― это такие функции, которые принимают функцию в качестве аргумента или возвращают функцию в качестве вывода.
Например, эти функции высшего порядка встроены в язык: Array.prototype.map
, Array.prototype.filter
и Array.prototype.reduce
.
Функции высшего порядка в действии
Давайте посмотрим на несколько примеров таких функций, и сравним код с функциями высшего порядка и без них.
Array.prototype.map
Метод map()
создаёт новый массив, вызывая callback-функцию, указанную в качестве аргумента, для каждого элемента входного массива. Метод map()
берёт каждое возвращённое значение от callback-функции и создаёт новый массив, используя эти значения.
Callback-функция, отправленная в метод map()
, принимает 3 аргумента: 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()
, принимает 3 аргумента element
, index
, и array
.
Давайте посмотрим примеры:
Пример №1
У нас есть массив, который содержит объекты со свойствами: имя и возраст. Нам нужно создать массив, который будет содержать только совершеннолетних (т.е. возраст больший или равный 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
выполняет callback-функцию для каждого элемента вызываемого массива, что в результате приводит к одному выходному значению. Метод reduce
принимает два параметра: 1) reducer-функцию (callback), 2) и опционально initialValue
.
Reducer-функция (callback) принимает 4 параметра: 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);
Reducer-функция вызывается для каждого элемента массива, а результат возвращённый reducer хранится в 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. Мы можем самостоятельно написать его, создав функцию высшего порядка.
Допустим, у нас есть строчный массив, и мы хотим конвертировать его в массив integer, в котором каждый элемент представляет длину строки из оригинального массива.
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
. После завершения цикла For, newArray
возвращает значение в lenArray
.
Заключение
Мы узнали, что такое функции высшего порядка и разобрали несколько из них, уже встроенных в язык. А также научились создавать их самостоятельно.
Не углубляясь в детали, о функциях высшего порядка можно сказать так: это функции, которые могут принимать функцию в качестве аргумента и даже, возвращать функцию. Такие функции похожи на обычные функции, но с дополнительной способностью ― получать и возвращать другие функции в качестве аргументов и вывода.
Перевод статьи Sukhjinder Arora : Understanding Higher-Order Functions in JavaScript