Пока мы рассмотрели только одну возможность объектов первого рода применительно к функциям — присвоение переменной. Самое интересное начинается, когда мы передаём одни функции в другие функции.

За примерами далеко ходить не придётся. Вспомним сортировку. В JavaScript есть метод sort, который сортирует массив. По умолчанию, этот метод сортирует массив весьма замысловатым способом: приводит каждый элемент массива к строковому типу и сравнивает полученные строки на основе порядка следования кодовых точек Unicode (подробнее см. в документации). Такая дефолтная сортировка не годится для большинства практических задач, ведь массивы могут быть совершенно разные, содержать элементы различных типов, которые надо сортировать по-разному.

Давайте вообразим ситуацию: на вход в программу приходит список пользователей, который нужно отсортировать по возрасту и вывести на экран.

const users = [
  { name: 'Igor', age: 19 },
  { name: 'Danil', age: 1 },
  { name: 'Vovan', age: 4 },
  { name: 'Matvey', age: 16 },
];

Дефолтную сортировку users.sort() отметаем сразу, как непригодную. Но выше я описал только лишь одну из тысяч возможных ситуаций. Мы можем захотеть сортировать по любому параметру (или даже по набору параметров) и в любом порядке. Сортировки нужны часто, и многие из них довольно сложны. Худшее, что можно начать делать — реализовывать функцию sort под каждую ситуацию. Так что же делать? В документации указано, что метод принимает один необязательный параметр — функцию, которая определяет порядок сортировки:

arr.sort([compareFunction])

Таким образом, метод sort сортирует элементы массива, используя для сравнения значений callback-функцию, предоставленную пользователем. Слово 'callback' означает, что наша задача — передать функцию (но не вызывать!), а вызывать её будет функция sort.

Общая идея состоит в том, что нам не нужно реализовывать алгоритм сортировки каждый раз для каждой ситуации, ведь он не меняется. Всё, что меняется — элементы, которые сравниваются между собой в процессе сортировки. И метод sort делегирует взаимодействие с этими элементами вызывающему коду:

Метод sort выполняет всю работу по непосредственному перемещению элементов в массиве. Но то, какой элемент больше или меньше, — зависит от вас. Достигается подобная схема за счёт той самой пользовательской функции, которая передаётся при вызове sort. Эта callback-функция принимает на вход два параметра — sort отдаёт в неё два элемента, которые она сравнивает в данный момент. В нашем случае элементы — пользователи. Ваша задача — внутри этой функции посчитать, что больше или меньше, и сделать следующее. Если элементы равны, то вы должны вернуть 0, если первый элемент больше второго, то считается, что они отсортированы правильно, и вы должны вернуть 1, иначе возвращается -1, а sort производит их сортировку.

Из кода видно, что внутри функции сравнение идёт по свойству age переданных пользователей. Нетрудно догадаться, что эта функция вызывается внутри sort множество раз (а именно на каждое сравнение). Как только она начнёт возвращать 1 для каждой пары элементов — сортировка завершена.

Функция sort относится к так называемым функциям высшего порядка (high order functions). Функции высшего порядка — это функции, которые либо принимают, либо возвращают другие функции, либо делают всё сразу. Такие функции, как правило, реализуют некий обобщённый алгоритм (например, сортировку), а ключевую часть логики делегируют вам через callback-функцию. Главный плюс от применения таких функций — серьёзное повышение коэффициента повторного использования кода.

В примере выше не обязательно создавать переменную для callback-функции. Говоря откровенно, их вообще редко записывают в переменные. Типичное использование выглядит как прямая передача функции в функцию:

users.sort((a, b) => {
  if (a.age === b.age) {
    return 0;
  }
  return a.age > b.age ? 1 : -1;
});

Потратьте немного времени, чтобы понять, где заканчивается одна функция и начинается другая. Подобный код мы начнём использовать уже со следующего урока.

Осталось рассмотреть то, как происходит вызов внутри. С точки зрения синтаксиса ничего нового не будет.

const say = (fn) => {
  const message = fn();
  console.log(message);
};
// или так:
// const say = (fn) => console.log(fn());

const myCallbackFn = () => 'hi!';
say(myCallbackFn); // => hi!
// или так:
// say(() => 'hi!');

Функция say делает вызов функции, находящейся внутри параметра fn. В нашем примере callback-функция возвращает строку, которая тут же выводится на экран.

Функции высшего порядка настолько удобны в большинстве языков, что практически целиком могут заменить использование тех же циклов. Например, канонический код на JS выглядит так:

const getJsFiles = dir => fs.readdirSync(dir)
  .filter(file => file.endsWith('js'))
  .map(file => path.resolve(dir, file));

В этом коде присутствует 2 функции высшего порядка (filter и map), 3 callback-функции и два прохода (это делают функции высшего порядка) по содержимому директории dir. Код весьма выразителен и лаконичен.

В следующих уроках мы рассмотрим три самые главные функции высшего порядка, которыми можно решать практически любые задачи. Две из них используются в примере выше, это map и filter, а третья — reduce (её ещё называют fold). Они все доступны в стандартной библиотеке JavaScript.


Дополнительные материалы

  1. Функция высшего порядка
Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Node, PHP, Python и Java.

Хекслет

Подробнее о том, почему наше обучение работает →