Если видео недоступно для просмотра, попробуйте выключить блокировщик рекламы.

Продолжая тему предыдущего урока, познакомимся с понятием "first-class citizen" или "объекты первого класса".

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

А теперь посмотрите внимательно на этот код: const x = () => console.log('hey'). Ничего необычного, вы видели и писали подобное множество раз. Если вы считаете, что в этом коде создаётся функция x, на самом деле это не так. Здесь происходят следующие две операции:

  1. Создание функции: () => console.log('hey')
  2. Создание константы со значением в виде функции: const x =

Этот момент нужно хорошо прочувствовать. Минимальное определение функции, которое только возможно, выглядит так: () => {}. Это пустая функция с пустым телом, которая не делает ничего. Присваивать её константе или нет — вопрос отдельный. Вполне допустимо написать и выполнить подобную программу: (a, b) => a + b;. Попробуйте поэкспериментировать в REPL.

Из примеров выше можно сделать вывод, что функция — тоже данные, ведь её можно присвоить константе. Рассмотрим это утверждение на практике:

const identity = v => v; // функция: v => v, константа: identity
console.log(identity(10)); // => 10

const z = identity;
console.log(z === identity); // => true

const x = 5;
console.log(z(x) === identity(x)); // => true

Выше определена функция, которую обычно называют identity. Она возвращает то значение, которое было ей передано. На практике такая функция нужна редко, но она очень хорошо подходит для демонстрации. Далее я создал новую константу, которую связал с той же функцией.

Главный вывод, который можно сделать из кода выше, заключается в том, что определение функции — это выражение, а значит оно возвращает значение, а именно — функцию. А раз определение функции — выражение, возвращающее функцию, то мы можем попробовать вызвать её без создания промежуточной константы:

// Определяем функцию v => v и тут же вызываем ее
(v => v)('run'); // => run

// Тот же код с использованием промежуточной константы.
// Попробуйте мысленно заменить `identity` на `v => v`, тогда
// получится (v => v)('run'). С выражениями так можно поступать всегда.
// const identity = v => v;
// identity('run'); // => run

Скобки вокруг определения функции — не какой-то особый синтаксис. Здесь они используются с той же целью, что и в арифметических операциях — для группировки. Без них выражение v => v('run') приобретает совсем другой смысл. В этом случае в нём создаётся функция, принимающая на вход другую функцию v и вызывающая её внутри с аргументом 'run'.

Попробуем усложнить:

identity(v => v)('run'); // => run
// (v => v)(v => v)('run') // => run

Первым идёт пример вызова функции по идентификатору, а во втором примере я заменил идентификатор на определение функции, сделав подстановку. Результат получился тот же самый. Ещё раз посмотрите на этот шаблон (<тут определение функции>)(). Попробуйте самостоятельно разобрать пример ниже:

((a, b) => a + b)(3, 2); // => 5
// const sum = (a, b) => a + b;
// sum(3, 2); // => 5

Теперь попробуем использовать функции как данные:

const sqrt = identity(Math.sqrt);
console.log(sqrt === Math.sqrt); // true
sqrt(4); // => 2

В первой строчке вызывается функция identity, в которую передаётся Math.sqrt. Результатом этого вызова будет всё та же функция Math.sqrt.

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

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

const sum = identity((a, b) => a + b);
sum(3, 5); // => 8

// const f = (a, b) => a + b;
// const sum = identity(f);
// sum(3, 5); // => 8

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

Возникает вопрос: есть ли имя у функций, определённых подобным образом? Имени нет даже у такой функции const f = () => {}. Мы просто связали константу с функцией, но сама функция ничего про константу не знает. Звучит слегка безумно, но это так. Ведь мы можем взять и связать эту функцию уже с другой константой. По этой причине такие функции часто называют анонимными. Другое распространенное название — лямбда-функция. Своим названием лямбда-функция обязана лямбда-исчислению (математическая формальная система, легшая в основу языков семейства lisp). Только в отличие от языков программирования, "лямбды" в лямбда-исчислении — всегда функции от одного аргумента, поэтому общее с функциями из js у них, в первую очередь, анонимность, и то, что они являются объектами первого класса.

Попробуем сделать что-нибудь полезное. Иногда встречается задача, в рамках которой нужно применить одну и ту же функцию несколько раз, например, так: Math.sqrt(Math.sqrt(16)). Создав функцию высшего порядка, можно упростить эту задачу. Рассмотрим пример с двойным применением одноаргументной функции:

const callTwice = (f, arg) => f(f(arg));

callTwice(Math.sqrt, 16); // => 2
callTwice(x => x ** 2, 3); // => 81
// const f = x => x ** 2;
// f(f(3));

callTwice применяет переданную функцию к своему аргументу два раза. Если расписать подробнее, то происходит следующее:

const res1 = f(arg);
const res2 = f(res1);
return res2;
Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Node, PHP, Python и Java.

Хекслет

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