Функции высшего порядка в JavaScript

https://blog.bitsrc.io/understanding-higher-order-functions-in-javascript-75461803bad
  • Перевод
  • Tutorial
Если вы занимаетесь изучением JavaScript, то вы, наверняка, сталкивались с понятием «функция высшего порядка» (Higher-Order Function). Может показаться, что это что-то очень сложное, но, на самом деле, это не так.

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. Кроме того, мы разобрались с тем, как создавать собственные функции высшего порядка.

Если выразить в двух словах суть функций высшего порядка, то можно сказать, что это функции, которые могут принимать другие функции в качестве аргументов и возвращать другие функции в качестве результатов своей работы. Работа с другими функциями в функциях высшего порядка выглядит так же, как работа с любыми другими объектами.

Уважаемые читатели! Приходится ли вам писать собственные функции высшего порядка?

  • +19
  • 18k
  • 8

RUVDS.com

1006,00

RUVDS – хостинг VDS/VPS серверов

Поделиться публикацией

Похожие публикации

Комментарии 8
    +6
    Ваши статьи дублируются, хоть и перевод разный :)

    habr.com/company/plarium/blog/428612
      +2
      Ваша статья дублирует эту: habr.com/company/plarium/blog/428612
      Плариум были быстрее на 11 минут :)

      впредь буду обновлять страницу перед отправкой комментария.
      +7
      Не хватает примеров, где функция возвращает функцию
        +1
        … которая, в свою очередь, принимает другую функцию и возвращает ещё одну.
        +2
        Всё новое — это хорошо забытое старое. В 2006 году Джоэл Спольский написал статью «Может ли ваш язык программирования делать это?», и как раз на примере JavaScript показал элементы функционального программирования в этом языке.

        И чтобы уже 2 раза не ходить, цитата:
        Среди языков, поддерживающих функциональное программирование, можно отметить JavaScript, Haskell, Clojure, Scala и Erlang.

        Я добавлю Delphi — он также уже умеет кое-что из функциональщины. Более, того, в версии 10.3, которая ожидается уже в этом месяце, появится декларирование переменных по месту использования и вывод типов. Можно будет писать что-то вроде:

        var Proc := (function: TProc begin
            var n := 0;
            Exit(procedure begin
              Write(n);
              Inc(n);
            end);
          end)();
          Proc;

        «Дo чегo ж на нашегo Буншу JavaScript пoхoж!» :)
          0
          Тогда и я пополню список Rust-ом, он может в ФВП и вообще в функцинальщину:
          fn is_odd(n: u32) -> bool { n % 2 == 1 }
          
          fn main() {
            println!("Найдём сумму всех нечётных квадратов чисел до 1000");
            let upper = 1000;
            let sum_of_squared_odd_numbers: u32 =
              (0..).map(|n| n * n)                          // все квадраты натуральных чисел
                .take_while(|&n_squared| n_squared < upper) // до верхнего предела
                .filter(|&n_squared| is_odd(n_squared))     // те что нечётные
                .fold(0, |acc, n_squared| acc + n_squared); // сложим их
            println!("Результат: {}", sum_of_squared_odd_numbers); // 5456
          }

          0
          Это так из-за того, что в JavaScript, как и в других языках, поддерживающих функциональное программирование, функции являются объектами.

          А без объектов функционального программирования не бывает?

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое