21 вересня 2023 р.

Функціональні вирази

Для JavaScript функція — це не “магічна мовна структура”, а значення особливого ґатунку.

Синтаксис, що ми раніше використовували, називається Оголошення Функції (Function Declaration):

function sayHi() {
  alert( "Привіт" );
}

Існує й інший синтаксис для створення функції, що називають Функціональним Виразом (Function Expression).

Він дозволяє створювати функцію всередині будь-якого виразу.

Наприклад:

let sayHi = function() {
  alert( "Привіт" );
};

Ми бачимо змінну sayHi. Як значення їй присвоюється нова функція function() { alert("Привіт"); }.

Оскільки створення функції відбувається в контексті присвоєння виразу (праворуч від =), це називається Функціональним виразом (Function Expression).

Зауважте, після ключового слова function немає назви функції. Для функціональних виразів це допустимо.

В цьому прикладі, ми одразу присвоюємо функцію цій змінній, тож, інакше кажучи, код в прикладі “створює функцію і кладе її в змінну sayHi”.

В складніших ситуаціях, з якими ми стикнемося пізніше, функцію можна створити та відразу ж викликати або запланувати її на відтерміноване виконання. Такі функції ніде не зберігаються, тому вони залишаються анонімними.

Функція – це значення

Повторімо: немає значення, яким чином створено функцію, функція – це завжди значення. В обох прикладах вище, функція зберігається в змінній sayHi.

Ми навіть можемо вивести це значення, використовуючи alert:

function sayHi() {
  alert( "Привіт" );
}

alert( sayHi ); // показує код функції

Зауважте, що останній рядок не викликає функцію тому, що після sayHi немає дужок. Існують мови програмування, в яких будь-яке звертання до імені функції спричиняє її виконання, але JavaScript — не одна з них.

У JavaScript функція — це значення, тому ми можемо поводитись з нею, як і з іншими значеннями. Код вище показує її рядкове представлення — вихідний код.

Звичайно, функція — особливе значення, у тому сенсі, що ми можемо здійснити її виклик за допомогою дужок: sayHi().

Але це все-таки значення. Тому ми можемо працювати з нею, як і з іншими значеннями.

Скажімо, ми можемо скопіювати функцію в іншу змінну:

function sayHi() {   // (1) створюємо
  alert( "Привіт" );
}

let func = sayHi;    // (2) копіюємо

func(); // Привіт     // (3) викликаємо копію (працює!)
sayHi(); // Привіт    //     ось так теж спрацює (а чому ні?)

Розглянемо детально, що тут відбулось:

  1. Оголошення Функції (1) створює саму функцію і кладе її значення у змінну sayHi.
  2. Рядок (2) копіює це значення в змінну func. Ще раз зауважте: після sayHi немає дужок. Якби вони там були, тоді func = sayHi() записав би результат виклику sayHi() у func, а не саму функцію sayHi.
  3. Тепер ми можемо викликати функцію двома шляхами: sayHi() або func().

Також ми могли використати Функціональний Вираз у першому рядку, щоб визначити sayHi:

let sayHi = function() { // (1) створити
  alert( "Привіт" );
};

let func = sayHi;
// ...

Результат був би таким самим.

Для чого потрібна крапка з комою в кінці?

Ви можете запитати, чому Функціональний Вираз містить крапку з комою ; в кінці, а Оголошення Функції — ні:

function sayHi() {
  // ...
}

let sayHi = function() {
  // ...
};

Відповідь проста: тут Функціональний Вираз (у вигляді function(…) {…}) створений всередині інструкції присвоєння: let sayHi = …;. Крапка з комою ; не є частиною синтаксису функції. Вона є частиною інструкції, тому її рекомендується ставити в кінці інструкції.

Крапку з комою доцільно було б поставити й для простішої інструкції, наприклад для let sayHi = 5;.

Колбеки (функції зворотного виклику)

Розглянемо інші приклади передачі функції як значення та використання Функціональних Виразів.

Для цього напишемо функцію ask(question, yes, no) з трьома параметрами:

question
Текст запитання
yes
Функція, що буде викликатись, якщо відповідь “Так”
no
Функція, що буде викликатись, якщо відповідь “Ні”

Функція повинна поставити запитання question і, залежно від відповіді користувача, викликати yes() або no():

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

function showOk() {
  alert( "Ви погодились." );
}

function showCancel() {
  alert( "Ви скасували виконання." );
}

// використання: функції showOk, showCancel передаються як аргументи для ask
ask("Ви згодні?", showOk, showCancel);

Такі функції є досить практичними. Головна відмінність між функцією ask у реальних програмах та прикладі вище, полягає в тому, що перша може використовувати складніші способи взаємодії з користувачем, ніж звичайний confirm. У браузерах така функція зазвичай показує гарненьке модальне вікно з запитанням. Але це вже інша історія.

Аргументи showOk та showCancel функції ask називаються функціями зворотного виклику або просто колбеками.

Суть полягає в тому, що ми передаємо функцію та очікуємо, що вона буде викликана (англ. “called back”) пізніше, якщо це буде потрібно. У нашому випадку, showOk стає колбеком, якщо відповідь — “yes”, а showCancel, якщо відповідь — “no”.

Ми можемо використати Функціональний Вираз, щоб записати ту ж саму функцію коротше:

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

ask(
  "Ви згодні?",
  function() { alert("Ви погодились."); },
  function() { alert("Ви скасували виконання."); }
);

У цьому прикладі функції оголошені всередині виклику ask(...). Вони не мають власного ім’я, тому називаються анонімними. До таких функцій не можна доступитись поза ask (бо вони не присвоєні змінним), але це саме те, що нам потрібно.

Подібний код, що з’явився в нашому скрипті є природним, в дусі JavaScript.

Функція — це значення, що відображає “дію”

Звичайні значення, як-от: рядки або числа, відображають дані

Функцію, з іншого боку, можна сприймати як дію.

Ми можемо передавати її від змінної до змінної і викликати коли заманеться.

Функціональний Вираз проти Оголошення Функції

Сформулюймо ключові відмінності між Оголошенням Функції та Функціональним Виразом.

По-перше, синтаксис: як розрізняти їх в коді.

  • Оголошення Функції: функція оголошується окремою конструкцією “function…” в основному потоці коду.

    // Оголошення Функції
    function sum(a, b) {
      return a + b;
    }
  • Функціональний Вираз: функція створюється як частина іншого виразу чи синтаксичної конструкції. Нижче, створення функції відбувається в правій частині “виразу присвоєння” =:

    // Функціональний Вираз
    let sum = function(a, b) {
      return a + b;
    };

Тонкіша відмінність в тому, коли функція буде створена рушієм Javascript.

Функціональний Вираз буде створено тільки тоді, коли до нього дійде виконання і тільки після цього він може бути використаний.

Щойно потік виконання досягне правої частини у присвоєнні let sum = function…, функцію буде створено і з цього моменту її можна буде використати (присвоїти змінній, викликати тощо).

У випадку з Оголошенням Функції все інакше.

Синтаксис Оголошення Функції дозволяє викликати функцію раніше, ніж вона були визначена в коді

Наприклад, глобальне Оголошення Функції буде доступним з будь-якого місця в скрипті.

Така поведінка спричинена особливостями внутрішніх алгоритмів. Коли JavaScript готується до виконання скрипту, він спочатку шукає всі глобальні Оголошення Функцій і на їх основі створює функції. Цей процес можна вважати “фазою ініціалізації”.