Software

Вы узнаете, что такое функции высшего порядка и как их применять в 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.mapArray.prototype.filter и Array.prototype.reduce .

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

Давайте посмотрим на несколько примеров таких функций, и сравним код с функциями высшего порядка и без них.

Array.prototype.map

Метод map() создаёт новый массив, вызывая callback-функцию, указанную в качестве аргумента, для каждого элемента входного массива. Метод map()берёт каждое возвращённое значение от callback-функции и создаёт новый массив, используя эти значения.

Callback-функция, отправленная в метод map(), принимает 3 аргумента: elementindex, и 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 аргумента elementindex, и 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 параметра: accumulatorcurrentValuecurrentIndexsourceArray.

Если параметр 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