Колбэки: функция как аргумент

функции≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

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

Разделы темы:·

Что функция — обычное значение, разбирала статья «Функциональные выражения»; на этом стоит вся нынешняя, поэтому повторим коротко. Объявление function greet() { ... } не просто «учит» язык новому слову: оно создаёт значение и кладёт его в переменную greet — примерно так же, как let x = 5 кладёт туда число. Разница одна: это значение умеет выполняться.

Отсюда правило, которое стоит запомнить сразу: greet — это сама функция, а greet() — результат её вызова. Первое можно скопировать, положить в массив, передать куда угодно. Второе — уже строка, число или undefined, и функцией больше не является.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

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

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Функция, переданная ради того, чтобы её вызвали позже, и есть колбэк (от английского call back — «перезвонить»). Заметьте: аргумент action нигде не описан подробнее — контракт держится на договорённости, что его вызовут с одним числом.

Если поставить скобки при передаче, функция выполнится немедленно, а дальше уйдёт её результат. Для функции без return это undefined — вызывать потом будет нечего.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Метод forEach обходит массив и на каждом шаге вызывает переданную функцию, подставляя ей значение и индекс. Сам цикл спрятан внутри метода — снаружи остаётся только описание того, что делать с элементом. Сами методы массива здесь — удобный пример, а разбирает их статья «Методы массивов: map, filter, reduce».

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

У sort задача обратная: он умеет переставлять элементы, но не знает, что для вас «меньше». Это и сообщает компаратор — колбэк, который получает два элемента и возвращает отрицательное число, если первый должен идти раньше, положительное — если позже, и ноль при равенстве. Без компаратора sort сравнивает элементы как строки, поэтому числа выстраиваются не по величине.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

setTimeout — не про массивы, а про время. Он принимает функцию и задержку в миллисекундах, запоминает эту пару и сразу возвращает управление. Ваш код продолжает выполняться, а колбэк ждёт своего момента.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Порядок вывода не совпадает с порядком строк — и это не сбой, а суть приёма. Вызвать функцию прямо сейчас было бы бессмысленно: важен именно момент, а выбирает его не ваш код, а таймер. Оба таймера и их ловушки — в статье «setTimeout и setInterval».

Колбэк — не особый вид функции, а её роль. Одну и ту же bark можно вызвать самому, отдать в setTimeout или в forEach: колбэком она становится в момент передачи.

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

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Так устроена половина стандартной библиотеки и почти все браузерные API: map, filter, обработчики кликов, ответы сервера. Везде повторяется один приём — общий алгоритм в чужом коде, конкретное поведение в вашей функции.

Для однострочных колбэков есть сокращённая форма — стрелочная функция; её разбирала прошлая статья, «Стрелочные функции». Здесь она стоит рядом с function затем, чтобы обе записи читались одинаково легко: это одно и то же значение-функция.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

  • Колбэк передают именем без скобок: скобки — это вызов, и в чужую функцию уехал бы уже его результат.
  • Колбэк — функция, переданная в другую функцию, чтобы та вызвала её сама в нужный момент.
  • forEach прячет цикл и вызывает колбэк на каждом элементе; sort вызывает компаратор для пары элементов и по знаку результата расставляет их.
  • setTimeout показывает вторую сторону колбэков: код не ждёт, а функция выполняется позже, поэтому порядок вывода не совпадает с порядком строк.
  • Приём один и тот же везде: общий алгоритм живёт в чужом коде, а конкретное поведение вы подставляете своей функцией.

1/3В setTimeout(sayHi, 1000) имя функции пишут без скобок. Почему?

2/3Что окажется в массиве после a.sort() без компаратора, если a = [10, 9, 100]?

3/3Первой строкой стоит setTimeout с колбэком и задержкой 0, второй — обычный console.log("B"). Что напечатается первым?

Своя функция each

Напишите функцию each(list, action), которая ведёт себя как forEach: обходит массив и на каждом шаге вызывает action, передавая ему элемент и его индекс. Возвращать ничего не нужно.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Внутри — обычный цикл. Всё, что меняется от вызова к вызову, приходит в параметре action: each только решает, когда и с чем его вызвать.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Обратите внимание: порядок аргументов при вызове action(list[i], i) — часть договорённости. Именно поэтому в колбэке снаружи первым параметром идёт элемент, а вторым индекс.

Сортировка по длине слова

Отсортируйте массив слов от самого короткого к самому длинному. Понадобится компаратор — колбэк из двух параметров.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Компаратор должен вернуть отрицательное число, когда первое слово идёт раньше. Разность длин делает это сама: для «ай» и «банан» получится 2 − 5 = −3.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Чтобы получить обратный порядок, достаточно поменять местами a и b: return b.length - a.length;