Функции: параметры и возвращаемый результат
В цикле одно действие выполняется несколько раз. Но каталог потребует повторять его и в других местах: первоначальный показ, изменение темы, обновление данных. Функция даёт такому действию имя, принимает необходимые значения и возвращает результат. Благодаря этому мы описываем правило один раз, а используем там, где оно нужно.
Начнём с подписи курса. Нам уже известны название и количество уроков. Сейчас оформим их в строку без изменения исходных данных. У функции будет конкретная обязанность: получить два значения и создать одну подпись. Она не будет одновременно искать элементы страницы или менять выбор темы.
Определение и вызов
Следующий пример полностью заменяет app.js:
function formatCourse(title, lessons) {
return `${title}: уроков ${lessons}`;
}
const message = formatCourse('Современный JavaScript', 20);
console.log(message);
console.log(formatCourse('HTML и CSS', 16));
Ожидаются две подписи с количествами двадцать и шестнадцать. Определение функции записывает правило, но само по себе не создаёт эти сообщения. Работу запускает вызов с круглыми скобками. Значения в вызове называются аргументами, а имена title и lessons в определении — параметрами.
Параметры являются локальными именами функции. В первом вызове title связан с названием JavaScript, во втором — с названием HTML и CSS. Для нового вызова не нужно вручную очищать предыдущие значения. Это позволяет использовать одно правило независимо от того, где описан конкретный курс.
Инструкция return завершает текущий вызов и передаёт значение в место вызова. В первой строке применения результат сохраняется в message; во второй передаётся сразу в console.log. В обоих случаях функцию можно использовать для другой цели без переписывания её тела: например, позднее присвоить результат свойству textContent.
Если заменить return на внутренний console.log, результат вызова станет undefined. Сообщение появится, но сохранить или показать именно строку в другом месте не получится. Поэтому различайте «функция печатает» и «функция возвращает»: это две разные обязанности, хотя в консоли их можно случайно принять за один результат.
Один источник правила
Добавим проверку совпадения темы отдельной функцией. Это полный пример; знакомое правило отбора теперь имеет имя и явные входы.
function matchesTopic(courseTopic, selectedTopic) {
return selectedTopic === 'all' || courseTopic === selectedTopic;
}
console.log(matchesTopic('frontend', 'all'));
console.log(matchesTopic('frontend', 'publishing'));
Ожидаются true и false. Функция не читает глобальную переменную selectedTopic: вызывающий код передаёт выбор напрямую. Это упрощает самостоятельное наблюдение. Чтобы рассмотреть другой вариант, достаточно изменить аргумент, а не выяснять, кто ранее изменил общую переменную.
Названия параметров поясняют порядок аргументов. Случайно поменяв их местами, можно получить правдоподобный, но неверный результат: вариант all относится именно к выбранной теме, а не к теме курса. Позднее объект параметров поможет делать некоторые интерфейсы ещё яснее, однако сейчас полезно увидеть значение порядка обычных аргументов.
Проверки типов здесь пока нет: функция предполагает корректные строки от программы. Когда вход придёт из JSON, проверим схему на границе получения. Не обязательно дублировать одинаковую проверку во всех маленьких функциях, если она уже проведена до обработки; важно понимать, на какой предпосылке основано тело.
Значение по умолчанию
Вызов может не передать аргумент. Для такого случая у параметра допускается начальное значение:
function matchesTopic(courseTopic, selectedTopic = 'all') {
return selectedTopic === 'all' || courseTopic === selectedTopic;
}
console.log(matchesTopic('frontend'));
console.log(matchesTopic('frontend', 'publishing'));
Первый результат ожидается истинным. Значение по умолчанию применяется, если соответствующий аргумент отсутствует или равен undefined. Явное null не превращается автоматически в all. Поэтому начальное значение не является универсальной проверкой любых неверных данных.
Выбирайте значение по умолчанию только там, где у него есть предметный смысл. Для отсутствующей темы «все темы» удобно, а для отсутствующего названия подставление «Новый курс» может скрыть повреждённую запись. Решение зависит от обязанности функции, а не от желания убрать любую ошибку.
Формы объявления, параметры и область функции подробно описаны в MDN о функциях. Для нашей программы достаточно обычного объявления и короткой стрелочной формы; особенности this пока не требуются.
Функция как значение
Функцию можно передать другой функции. В следующем уроке это поможет обрабатывать элементы массива. Короткое правило отбора можно записать стрелочной формой. Для этого замените прежнее объявление function matchesTopic следующим объявлением const matchesTopic; одновременно обе формы в том же файле не оставляйте:
const matchesTopic = (courseTopic, selectedTopic) =>
selectedTopic === 'all' || courseTopic === selectedTopic;
console.log(matchesTopic('frontend', 'frontend'));
После стрелки находится выражение, поэтому его результат возвращается неявно. Если вместо выражения открыть блок {}, понадобится явный return. Отсутствие возврата — частая причина того, что будущая фильтрация неожиданно исключает все карточки.
Обычная и стрелочная функции имеют различия, поэтому нельзя утверждать, что формы взаимозаменяемы во всех задачах. В нашем правиле нет собственного this, конструктора и особых аргументов; стрелочная запись используется только для небольшого вычисления. Выбирайте понятную форму, а не наиболее короткую любой ценой.
Для самостоятельной работы измените формат подписи так, чтобы число выводилось перед названием. Затем проверьте мысленно два вызова: оба должны использовать новое правило. Не меняйте количества и не добавляйте множественные варианты склонения сейчас — мы изучаем передачу значений и получение результата, а языковое оформление можно расширить независимо.
Возврат и побочные действия
Функция форматирования получает одинаковые аргументы и создаёт одинаковую строку. Она не меняет курс и не хранит скрытый счётчик вызовов. Такое свойство помогает разбирать программу: результат зависит от входа, а не от того, сколько раз пользователь уже открывал форму. В учебном каталоге вычисления по возможности сохраняем именно такими.
Это не означает запрет любых побочных действий. Создание карточек позднее изменит документ, а загрузка обратится к серверу. Но смешение этих действий с расчётом подписи сделало бы функцию неудобной: чтобы узнать строку, пришлось бы одновременно менять страницу. Разные обязанности лучше назвать разными функциями и связать из управляющего кода.
После return текущий вызов не продолжает выполнение обычных последующих инструкций. Если нужно проверить условие и вернуть ранний результат, оставшиеся действия должны относиться к другой ветке. В частности, нельзя рассчитывать, что сообщение, записанное после безусловного return, будет показано. Для длинной возвращаемой строки также сохраняйте выражение рядом с return: перенос сразу после ключевого слова может столкнуться с правилами автоматической вставки точки с запятой. Наш короткий листинг избегает этой неоднозначности и показывает возвращаемое значение явно.
Имена функций полезно выбирать по действию и результату. formatCourse создаёт оформление, matchesTopic отвечает на вопрос совпадения. Название вроде process не помогает понять, возвращается ли строка, boolean или изменённый объект. Пока функций мало, это кажется необязательным; после разделения на файлы название станет первым объяснением зависимости.
Возвращаемое значение может быть использовано несколько раз, но повторный вызов тоже выполнит тело снова. Если позже внутри функции появится дорогая загрузка или изменение страницы, такой повтор уже не будет безразличным. Поэтому чистое вычисление подписи и отдельное действие интерфейса удобно разделить с самого начала.
Аргументы не обязаны иметь те же имена, что параметры. Переменная вызывающего кода courseTitle может попасть в параметр title; связь задаётся позицией и выполненным вызовом. После возврата локальные имена функции не становятся новыми глобальными переменными. Это позволяет переиспользовать понятные внутренние названия, не заставляя весь проект делить одну область. Не пытайтесь читать параметр за пределами определения ради получения результата — используйте то, что функция вернула.
У нас появились две операции: создание подписи и проверка темы. Обе получают нужные данные через параметры. Дальше объединим четыре значения в массив и передадим функции отдельные элементы, сохраняя ясную границу между исходными данными и результатом их обработки.