Function Expression, анонимные и callback-функции

Синтаксис, который мы использовали на предыдущем занятии для объявления функций, называется Function Declaration (объявление функции):

function showMsg() {
         console.log("Hello!");
}

Существует ещё один синтаксис создания функций, который называется Function Expression (функциональное выражение). Оно выглядит вот так:

let showMsg = function() {
         console.log("Hello!");
};

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

let <переменная> = <значение>;

Значение этой переменной можно вывести в консоль и посмотреть чему она равна:

console.log( showMsg );
console.log( typeof showMsg );

Да, переменная showMsg содержит искомую функцию, точнее ссылку на функцию. А вот тип переменной – это специальный тип данных, означающий функцию. На самом деле – это объект JavaScript специального вида. Если мы вернем запись функции по синтаксису Function Declaration, то при выводе в консоль ничего не изменится. Это как раз и говорит, что оба объявления подобны. Подобны, но не идентичны! Об их различиях речь пойдет позже в этом занятии.

Вернем запись в виде Function Expression. Здесь у нас имеется переменная showMsg, которая ссылается на функцию. А раз так, то можем ли мы ее скопировать в другую переменную и получить копию этой функции? Почти, только мы скопируем не саму функцию, а ссылку на нее, в результате обе переменные будут обращаться к одной и той же функции:

let show = showMsg;

Теперь вызовем функцию через эти переменные. Для этого после их имени нужно поставить круглые скобки:

show();		// 1-й вызов функции
showMsg();	//2-й вызов функции

Запомните этот момент! Без круглых скобок мы работаем с переменными show и showMsg, а с круглыми скобками – вызываем соответствующие функции. Этот синтаксис следует правильно понимать и помнить.

Далее, все это будет работать, если функцию объявить и как Function Declaration, так как обе записи – это, по сути, одно и тоже: создается переменная showMsg, которая ссылается на объект-функцию.

Ну, хорошо, разобрались. Но зачем нужно объявление по Function Expression? Для ответа на этот вопрос приведем такой пример. Предположим, у нас есть функция, которая запрашивает у пользователя согласие на использование cookies в браузере. И реализуем ее так:

function agreeCookies(question, yes, no) {
  if (confirm(question)) yes();
  else no();
}
 
function agreeYes() { 
   console.log("Вы приняли соглашение о cookies");
}
 
function agreeNo() {
   console.log("Вы отказались от использования cookies");
}
 
agreeCookies("Наш сайт использует cookies. Нам нужно ваше согласие", agreeYes, agreeNo);

Смотрите, у функции agreeCookies три параметра: первый отвечает за текст сообщения, а два других – содержат ссылки на функции, которые вызываются при согласии или несогласии с использованием cookies. В результате мы получили довольно гибкий код: вся логика по обработке вынесена в отдельные функции, которые могут обрабатывать запрос самыми разными способами. При этом сама функция agreeCookies остается неизменной. Аргументы yes, no, которые содержат ссылки на функции, еще называют callback-функциями (от англ. call back – обратный вызов), то есть, отложенный вызов, если в нем будет необходимость.

Данный пример можно записать и короче, вот так:

function agreeCookies(question, yes, no) {
  if (confirm(question)) yes();
  else no();
}
 
agreeCookies( 
    "Наш сайт использует cookies. Нам нужно ваше согласие",  
    function () { console.log("Вы приняли соглашение о cookies"); },
    function () { console.log("Вы отказались от использования cookies"); }
);

Мы здесь непосредственно при вызове функции agreeCookies передаем ей все необходимые параметры: строку запроса и две функции, которые объявлены прямо внутри вызова agreeCookies. У них нет имен, поэтому такие функции называются анонимными. Соответственно, эти функции доступны и существуют только в момент вызова agreeCookies и не существуют за ее пределами.

Программисты на JavaScript очень часто используют такой подход при написании скриптов. И это считается хорошим стилем в программировании.

Итак, можно заключить, что имя функции – это переменная, которая содержит специальные данные – функцию. Эти данные можно воспринимать не как обычные статичные значения, а как некое действие, которое может быть выполнено в любом месте скрипта.

Теперь посмотрим на отличия между двумя объявлениями функций по Function Declaration и Function Expression. Как мы говорили на предыдущем занятии, функции объявленные по Function Declaration:

function agreeCookies(question) {
     if (confirm(question)) console.log("Вы приняли соглашение о cookies");
     else console.log("Вы отказались от использования cookies");
}

Могут вызываться и до и после их объявления, то есть вот так:

agreeCookies("Наш сайт использует cookies. Нам нужно ваше согласие");
 
function agreeCookies(question) {
     if (confirm(question)) console.log("Вы приняли соглашение о cookies");
     else console.log("Вы отказались от использования cookies");
}
 
agreeCookies("Наш сайт использует cookies. Нам нужно ваше согласие");

Если же мы объявим эту же функцию как Function Expression:

let agreeCookies = function(question) {
   if (confirm(question)) console.log("Вы приняли соглашение о cookies");
   else console.log("Вы отказались от использования cookies");
};

То вызвать ее до объявления переменной agreeCookies будет невозможно, так как она не будет существовать. А вот после объявления вызовется без проблем.

Далее, если у нас включен строгий режим "use strict" (а мы договорились его использовать), то объявления по Function Declaration видны только внутри блока кода, в котором они объявлены. Например, вот в такой программе:

let age = prompt("Сколько вам лет?", 18);
 
if(age < 18) {
   function setAccess() {
         console.log("Доступ запрещен: вы слишком молоды");
   }
}
else {
   function setAccess() {
         console.log("Доступ разрешен");
   }
}
 
setAccess();

В блоках if else объявляется функция setAccess, которую предполагается вызвать и определить: разрешать доступ или нет. Но здесь возникнет ошибка, так как функция не будет существовать за пределами блоков if else. Это ограничение можно обойти, используя Function Expression:

let age = prompt("Сколько вам лет?", 18);
let setAccess = null;
 
if(age < 18) {
    setAccess = function () {
       console.log("Доступ запрещен: вы слишком молоды");
    };
}
else {
    setAccess = function () {
       console.log("Доступ разрешен");
    };
}
 
setAccess();

Теперь переменная setAccess будет содержать ссылку на функцию, в зависимости от указанного возраста и после блока if else вызывается без проблем. Кстати, эту же программу можно записать гораздо короче, вот так:

let age = prompt("Сколько вам лет?", 18);
let setAccess = (age < 18) ?
    function () {console.log("Доступ запрещен: вы слишком молоды"); } :
    function () { console.log("Доступ разрешен"); };
 
setAccess();

Здесь переменной setAccess будет присваивается анонимная функция в зависимости от указанного возраста.

Так как же понимать: когда использовать объявления по Function Declaration, а когда по Function Expression? Обычно, поступают так. В первую очередь следует рассматривать синтаксис Function Declaration. Он дает больше свободы в организации кода: функции, объявленные таким образом, можно вызывать и до и после их объявления. К тому же такие функции проще воспринимаются в тексте скрипта.

Объявления по Function Expression следует использовать только тогда, когда по каким-то причинам нам не подходит объявление по Function Declaration. Например, при реализации callback-функций. Или вызова функции за пределами блока их объявления. Все эти примеры мы с вами только что рассматривали.

Видео по теме