[Перевод] Функции высшего порядка в JavaScript: что это такое?

Представляем вам перевод статьи Sukhjinder Arora, опубликованной на ресурсе Bits and Pieces. Узнайте под катом о функциях высшего порядка в JavaScript и о некоторых других функциях, встроенных в этот язык.

lttwobifimufwalp_1aqoax6pq8.jpeg
Фото 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 принимает два аргумента:

  1. callback-функцию, которую требуется обработать;
  2. необязательный параметр 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. Не стесняйтесь — комментируйте, если у вас есть вопросы! Я буду рад помочь. :)

© Habrahabr.ru