Колбэки: функция как аргумент
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Функцию в JavaScript можно не только вызвать — её можно передать дальше, как передают число или строку. Функция, которую отдали в чужие руки, чтобы её вызвали в нужный момент, называется колбэком.
Что функция — обычное значение, разбирала статья «Функциональные выражения»; на этом стоит вся нынешняя, поэтому повторим коротко. Объявление function greet() { ... } не просто «учит» язык новому слову: оно создаёт значение и кладёт его в переменную greet — примерно так же, как let x = 5 кладёт туда число. Разница одна: это значение умеет выполняться.
Отсюда правило, которое стоит запомнить сразу: greet — это сама функция, а greet() — результат её вызова. Первое можно скопировать, положить в массив, передать куда угодно. Второе — уже строка, число или undefined, и функцией больше не является.
Раз функция — обычное значение, её можно принять параметром. Принимающая сторона не знает и не хочет знать, что именно ей дали: она просто в нужный момент ставит скобки и вызывает. Ниже repeat управляет счётчиком, а что делать на каждом шаге — решает переданная функция.
Функция, переданная ради того, чтобы её вызвали позже, и есть колбэк (от английского call back — «перезвонить»). Заметьте: аргумент action нигде не описан подробнее — контракт держится на договорённости, что его вызовут с одним числом.
Если поставить скобки при передаче, функция выполнится немедленно, а дальше уйдёт её результат. Для функции без return это undefined — вызывать потом будет нечего.
Метод forEach обходит массив и на каждом шаге вызывает переданную функцию, подставляя ей значение и индекс. Сам цикл спрятан внутри метода — снаружи остаётся только описание того, что делать с элементом. Сами методы массива здесь — удобный пример, а разбирает их статья «Методы массивов: map, filter, reduce».
У sort задача обратная: он умеет переставлять элементы, но не знает, что для вас «меньше». Это и сообщает компаратор — колбэк, который получает два элемента и возвращает отрицательное число, если первый должен идти раньше, положительное — если позже, и ноль при равенстве. Без компаратора sort сравнивает элементы как строки, поэтому числа выстраиваются не по величине.
setTimeout — не про массивы, а про время. Он принимает функцию и задержку в миллисекундах, запоминает эту пару и сразу возвращает управление. Ваш код продолжает выполняться, а колбэк ждёт своего момента.
Порядок вывода не совпадает с порядком строк — и это не сбой, а суть приёма. Вызвать функцию прямо сейчас было бы бессмысленно: важен именно момент, а выбирает его не ваш код, а таймер. Оба таймера и их ловушки — в статье «setTimeout и setInterval».
Колбэк — не особый вид функции, а её роль. Одну и ту же bark можно вызвать самому, отдать в setTimeout или в forEach: колбэком она становится в момент передачи.
Колбэк выносит за скобки то, что меняется. Функция findFirst ниже умеет обходить список и вовремя останавливаться, а что считать подходящим элементом — решает тот, кто её вызывает. Один алгоритм, сколько угодно условий.
Так устроена половина стандартной библиотеки и почти все браузерные API: map, filter, обработчики кликов, ответы сервера. Везде повторяется один приём — общий алгоритм в чужом коде, конкретное поведение в вашей функции.
Для однострочных колбэков есть сокращённая форма — стрелочная функция; её разбирала прошлая статья, «Стрелочные функции». Здесь она стоит рядом с function затем, чтобы обе записи читались одинаково легко: это одно и то же значение-функция.
- Колбэк передают именем без скобок: скобки — это вызов, и в чужую функцию уехал бы уже его результат.
- Колбэк — функция, переданная в другую функцию, чтобы та вызвала её сама в нужный момент.
- 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, передавая ему элемент и его индекс. Возвращать ничего не нужно.
Внутри — обычный цикл. Всё, что меняется от вызова к вызову, приходит в параметре action: each только решает, когда и с чем его вызвать.
Обратите внимание: порядок аргументов при вызове action(list[i], i) — часть договорённости. Именно поэтому в колбэке снаружи первым параметром идёт элемент, а вторым индекс.
Сортировка по длине слова
Отсортируйте массив слов от самого короткого к самому длинному. Понадобится компаратор — колбэк из двух параметров.
Компаратор должен вернуть отрицательное число, когда первое слово идёт раньше. Разность длин делает это сама: для «ай» и «банан» получится 2 − 5 = −3.
Чтобы получить обратный порядок, достаточно поменять местами a и b: return b.length - a.length;