Як ми вже знаємо, функція в JavaScript – це значення.
Кожне значення в JavaScript має тип. Який тип функції?
У JavaScript, функції є об’єктами.
Ви можете уявити собі функції, як “об’єкти, що можна викликати, та які можуть виконувати якісь дії”. Ми можемо не тільки викликати їх, але й ставитися до них як до об’єктів: додавати/видаляти властивості, передавати за посиланням тощо.
Властивість “name”
Функціональні об’єкти містять деякі зручні властивості.
Наприклад, назва функції доступна як властивість “name”:
function sayHi() {
alert("Привіт");
}
alert(sayHi.name); // sayHi
Що доволі смішно, логіка присвоєння “name” досить розумна. Вона працює так, що призначає правильне ім’я функції, навіть якщо функція була створена без імені, а потім була негайно призначена:
let sayHi = function() {
alert("Привіт");
};
alert(sayHi.name); // sayHi (є ім’я!)
Це також працює, якщо призначення виконується за допомогою значення за замовчуванням:
function f(sayHi = function() {}) {
alert(sayHi.name); // sayHi (працює!)
}
f();
У специфікації ця ознака називається “контекстне ім’я”. Якщо функція не надає власне ім’я, то в присвоєнні воно з’являється з контексту.
Методи об’єктів також мають назви:
let user = {
sayHi() {
// ...
},
sayBye: function() {
// ...
}
}
alert(user.sayHi.name); // sayHi
alert(user.sayBye.name); // sayBye
Проте тут немає ніякої магії. Є випадки, коли немає жодного способу з’ясувати правильну назву. У цьому випадку ім’я назви порожнє, як тут:
// функція створена всередині масиву
let arr = [function() {}];
alert( arr[0].name ); // <порожній рядок>
// рушій JavaScript не має можливості налаштувати правильну назву, тому в цьому випадку немає жодного значення
На практиці, однак, більшість функцій мають назву.
Властивість “length”
Існує ще одна вбудована властивість “length”, яка повертає кількість параметрів функції, наприклад:
function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}
alert(f1.length); // 1
alert(f2.length); // 2
alert(many.length); // 2
В останньому випадку ми бачимо, що параметри, які було зібрано rest оператором, не підраховуються.
Властивість length іноді використовується для інтроспекції у функціях, які працюють з іншими функціями.
Наприклад, у коді нижче функція ask приймає як аргумент запитання question та довільну кількість функцій-оброблювачів відповіді handler.
Після того, як користувач надає відповідь, функція викликає оброблювачі. Ми можемо передати два типи обробників:
- функція без аргументів, яка лише викликається, коли користувач дає позитивну відповідь.
- функція з аргументами, яка називається в будь-якому випадку, і повертає відповідь.
Щоб викликати handler правильно, ми розглядаємо властивість handler.length.
Ідея полягає в тому, що у нас є простий, синтаксис обробника без аргументів для позитивних випадків (найчастіший варіант), але також підтримуються універсальні обробники:
function ask(question, ...handlers) {
let isYes = confirm(question);
for(let handler of handlers) {
if (handler.length == 0) {
if (isYes) handler();
} else {
handler(isYes);
}
}
}
// Для позитивної відповіді, обидва обробники викликаються
// для негативної відповіді, тільки другий
ask("Запитання?", () => alert('Ти сказав так'), result => alert(result));
Це конкретний випадок так званого поліморфізму – обробка аргументів по-різному залежно від їх типу або, у нашому випадку залежно від length. Ця ідея використовується в бібліотеках JavaScript.
Кастомні властивості
Ми також можемо додати власні властивості.
Тут ми додаємо властивість counter для відстеження загальної кількості викликів:
function sayHi() {
alert("Привіт");
// давайте порахувати, скільки викликів функції ми зробили
sayHi.counter++;
}
sayHi.counter = 0; // початкове значення
sayHi(); // Привіт
sayHi(); // Привіт
alert( `Викликана ${sayHi.counter} рази` ); // Викликана 2 рази
Властивість, присвоєна функції, як sayhi.counter = 0 не визначає локальну змінну counter всередині цієї функції. Іншими словами, властивість counter та змінна let counter є двома незв’язаними речами.
Ми можемо використовувати функцію як об’єкт, зберігати властивості у ньому, але це не впливатиме на її виконання. Змінні – це не властивості функції і навпаки. Це два паралельні світи.
Властивості функцій можуть іноді замінити замикання. Наприклад, ми можемо переписати приклад функції лічильника з розділу Область видимості змінної, замикання використовуючи властивість функції:
function makeCounter() {
// замість:
// let count = 0
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
Зараз count зберігається в функції безпосередньо, а не у зовнішньому лексичному середовищі.
Це краще або гірше, ніж використання замикання?
Основна відмінність полягає в тому, що якщо значення count живе в зовнішній змінній, то зовнішній код не може отримати доступ до нього.Тільки вкладені функції можуть змінювати його. А якщо це значення присвоєно як властивість функції, то ми можемо отримати до нього доступ:
function makeCounter() {
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
counter.count = 10;
alert( counter() ); // 10
Таким чином, вибір реалізації залежить від наших цілей.
Named Function Expression
Named Function Expression, або NFE – це термін для Function Expressions, у якого є назва.
Наприклад, об’явімо звичайний Function Expression:
let sayHi = function(who) {
alert(`Привіт, ${who}`);
};
І додайте до нього назву:
let sayHi = function func(who) {
alert(`Привіт, ${who}`);
};
Чого ми досягли тут? Яка мета додаткової назви "func"?
Спочатку відзначимо, що у нас ще є Function Expression. Додавання назви "func" після function не робить оголошення функції у вигляді Functional Declaration, оскільки функція все є частиною виразу присвоєння.
Додавання такої назви нічого не порушує.
Функція все ще доступна як sayHi():
let sayHi = function func(who) {
alert(`Привіт, ${who}`);
};
sayHi("Іван"); // Привіт, Іван
Є дві важливі особливості назви func, через які воно дається:
- Вона дозволяє функції посилатися на себе.
- Вона не доступна за межами функції.
Наприклад, функція sayHi нижче викликає себе знову "Гість" якщо who не надається:
let sayHi = function func(who) {
if (who) {
alert(`Привіт, ${who}`);
} else {
func("Гість"); // використовує func для повторного виклику
}
};
sayHi(); // Привіт, Гість
// Але це не буде працювати:
func(); // Помилка, func не оголошена (недоступна за межами функції)
Чому ми використовуємо func? Можливо, просто використовувати sayHi для вкладеного виклику?
Насправді в більшості випадків ми можемо це зробити: