
Привет, Хабр! Я — Александр Дудукало, автор одноименного YouTube-канала. В прошлой статье мы разобрали ссылочный тип данных, который хранит не само значение, а ссылку на него в памяти. Сегодня используем полученные знания на практике и познакомимся с одной из самых важных концепций в JavaScript — callback-функциями. Подробности — под катом!
Особенности функций
Прежде чем дать определение, хочу поделиться двумя важными фактами о функциях в JavaScript. С их помощью вы сможете лучше усвоить новую тему.
Функцию можно вызвать, то есть запустить ее код. Для этого нужно указать идентификатор (имя переменной, в которой хранится ссылка на функцию) и круглые скобки. Конечно, есть и другие способы вызова, но в рамках этой статьи остановимся на самом популярном.
Функцию можно передать или присвоить. Поскольку функция — это ссылочный тип данных, то ее можно присвоить другой переменной через «=» или передать в качестве аргумента в другую функцию.
Давайте посмотрим на пример кода, который демонстрирует вызов функции и ее присвоение в другую переменную:
function sayHello(name) {
console.log(`Привет, ${name}`);
}
sayHello("Аня"); // Вызов функции
const sayHello2 = sayHello; // Присвоение ссылки на функцию в новую переменную
sayHello2("Ваня"); // Вызов функции

Пример выдуманный, но он хорошо показывает два ключевых момента: вызов функции и возможность передачи ссылки на нее в другую переменную.
Сначала вызывается sayHello, затем ее ссылка присваивается sayHello2, после чего функция успешно вызывается через новое имя, потому что обе переменные указывают на один объект. Обратите внимание, что возможность передавать аргументы при этом полностью сохраняется. Все работает абсолютно так же.
Думаю, стоит прояснить один момент. Глубоко в детали мы пока уходить не будем, но это важно понимать: sayHello — идентификатор, в котором хранятся ссылки на функции. Для простоты я буду называть его именем функции, но знайте, что они могут быть и безымянными (анонимными).
Теперь, когда мы разобрали ключевые особенности, можем перейти к определению, что такое callback-функции.

Бесплатный базовый курс по JS
Рассказываем, как работать с переменными, типами данных, функциями и многом другом!
Как работают callback-функции
Тот, кто работает с функциями, знает, что в нее можно передавать значения через параметр, если он предусмотрен. Например:
const DISCOUNT_AMOUNT = 100;
function applyDiscount(price) {
return price - DISCOUNT_AMOUNT;
}
console.log(
`Стоимость со скидкой: ${applyDiscount(200)}`
);

Все просто: функция получает стоимость товара, после чего возвращает в ответе цену со скидкой. Размер скидки в данном случае фиксирован.
Но что будет, если вы захотите рассчитать скидки в зависимости от определенных условий — например, с учетом времени, дня месяца, роли пользователя или стоимости товара. А еще — гибко управлять этим расчетом.
Один из вариантов решений этой задачи — использование callback-функции, переданной как аргумент в функцию вычисления стоимости. Смотрим код:
const MIN_PRICE_FOR_DISCOUNT = 500;
const DISCOUNT_AMOUNT = 100;
function calculateDiscountAmount(price) {
if (price >= MIN_PRICE_FOR_DISCOUNT) {
return DISCOUNT_AMOUNT;
}
return 0;
}
function applyDiscount(price, discountCalculator) {
return price - discountCalculator(price); // Вызов callback-функции
}
console.log(
`Стоимость со скидкой: ${applyDiscount(550, calculateDiscountAmount)}`
);

Внимательно изучите код и вспомните о двух фактах, о которых я говорил ранее. Функцию можно передать в другую как аргумент, не вызывая ее. А чтобы вызвать функцию внутри другой, можно обратиться по имени параметра и добавить круглые скобки. При этом передача аргументов полностью сохраняется.
Именно так работает callback-функция. Она передает данные в другую функцию как аргумент, которая вызывается внутри этой внешней функции в нужный момент для выполнения определенного действия. Рассмотрим ее особенности.
Callback передается по ссылке, поэтому при передаче мы указываем имя функции без скобок.
Момент вызова callback контролируется внешней функцией — то есть той, в которую callback передается как аргумент.
Callback может принимать аргументы и возвращать результат, как обычная функция.
Одна внешняя функция может работать с разными callback-функциями, делая код более гибким.
Итак, у нас есть функция applyDiscount, которая рассчитывает стоимость товара со скидкой. Благодаря callback-функции, у нас появляется возможность задавать способ вычисления этой скидки. Давайте добавим еще один вызов и способ расчета. Например, будем добавлять скидку, если сегодня понедельник:
const MIN_PRICE_FOR_DISCOUNT = 500;
const DISCOUNT_AMOUNT = 100;
function calculateDiscountAmount(price) {
if (price >= MIN_PRICE_FOR_DISCOUNT) {
return DISCOUNT_AMOUNT;
}
return 0;
}
// Новая функция для скидки по понедельникам
function calculateMondayDiscount() {
const today = new Date();
const isMonday = today.getDay() === 1; // 1 - понедельник
if (isMonday) {
return DISCOUNT_AMOUNT;
}
return 0;
}
function applyDiscount(price, discountCalculator) {
return price - discountCalculator(price); // Вызов callback-функции
}
console.log(
`Стоимость со скидкой: ${applyDiscount(550, calculateDiscountAmount)}`
);
console.log(
`Стоимость со скидкой по понедельникам: ${applyDiscount(750, calculateMondayDiscount)}`
);

Получилось круто, не правда ли? При этом мы расширяем возможности расчета скидки без изменения функции applyDiscount.
Для закрепления добавим еще одну функцию, которая подсчитывает стоимость с учетом всех скидок. Как видно на примере, кода становится чуть больше:
const MIN_PRICE_FOR_DISCOUNT = 500;
const DISCOUNT_AMOUNT = 100;
function calculateDiscountAmount(price) {
if (price >= MIN_PRICE_FOR_DISCOUNT) {
return DISCOUNT_AMOUNT;
}
return 0;
}
function calculateMondayDiscount() {
const today = new Date();
const isMonday = today.getDay() === 1; // 1 - понедельник
if (isMonday) {
return DISCOUNT_AMOUNT;
}
return 0;
}
// Функция для расчета общей скидки
function calculateTotalDiscount(price) {
return calculateDiscountAmount(price) + calculateMondayDiscount();
}
function applyDiscount(price, discountCalculator) {
return price - discountCalculator(price); // Вызов callback-функции
}
console.log(
`Стоимость со скидкой: ${applyDiscount(550, calculateDiscountAmount)}`
);
console.log(
`Стоимость со скидкой по понедельникам: ${applyDiscount(750, calculateMondayDiscount)}`
);
console.log(
`Итоговая стоимость с учетом скидок: ${applyDiscount(1000, calculateTotalDiscount)}`
);

Кстати, в этом коде не учитываются случаи, когда скидка может увести стоимость товара в ноль, а то и в отрицательное значение. Такого, конечно, быть не должно. Для закрепления темы подумайте, как это можно доработать.
В целом, эту задачу можно решить и другими способами, но на мой взгляд для простой демонстрации работы с callback-функциями этот пример подходит отлично.
Заключение
Callback-функции — это одна из фундаментальных концепций JavaScript, которая активно используется в разных сценариях. Вы будете постоянно встречать их при работе с таймерами, обработкой событий, запросами к серверу и, что особенно важно, при работе с массивами. Кстати, в следующей статье я расскажу о методах массивов — forEach, map и filter. На их примере тема callback-функций проявится особенно наглядно и применимо на практике.
В этой статье я намеренно затронул тему лишь в общих чертах, сосредоточившись на ключевой идее — передаче функции как аргумента. Примеры и объяснения максимально упрощены, чтобы помочь новичкам сделать первый шаг к пониманию этого важного механизма.
Если вы хотите глубже разобраться в нюансах callback-функций, увидеть больше практических примеров и понять, предлагаю посмотреть мое подробное видео на эту тему. Там мы разберем все то, что не поместилось в формат статьи, и ответим на ваши вопросы в комментариях. Всем пока!