Способи оголошення функцій
У JavaScript функція це значення, тому мова дає одразу кілька синтаксичних форм її створення, від класичного function до стрілок, генераторів і методів класу. Форми відрізняються не лише виглядом: від вибору залежать hoisting, поведінка this, наявність prototype і можливість виклику через new.
Теорія
TL;DR
- Function declaration піднімається (hoisting), має власний
thisі може бути конструктором. - Function expression доступне лише після присвоєння, буває анонімним або іменованим.
- Стрілочна функція бере
thisлексично, не маєarguments,prototypeі не працює зnew. - Окремі різновиди:
new Function(створення з рядка, не рекомендується),function*(генератор),async function(повертаєPromise). - Методи об'єктів і класів це також функції, але з підтримкою
superчерез[[HomeObject]]. - IIFE і
bind()це не нові синтаксичні форми, а способи одразу виконати функцію чи зафіксувати їїthis.
Швидкий приклад
function sum(a, b) { // 1. function declaration
return a + b;
}
const mul = function (a, b) { // 2. function expression
return a * b;
};
const inc = x => x + 1; // 3. arrow function
const obj = {
sayHi() { console.log('hi'); } // 4. method shorthand
};Класичні форми: declaration, expression, стрілка
1) Function declaration (оголошення функції)
function sum(a, b) {
return a + b;
}- Має hoisting: функцію можна викликати до її оголошення в коді.
- Має власний
thisпри виклику як функції або методу. - Можна використовувати як конструктор (
new Sum()).
2) Function expression (функціональний вираз)
const mul = function (a, b) {
return a * b;
};- Hoisting немає: функція доступна лише після присвоєння.
- Може бути анонімною або іменованою:
const fact = function factorial(n) { /* ... */ }; // named: the name is visible insideІм'я в іменованому виразі видно лише всередині самої функції, і воно зручне для рекурсії та для читабельного стектрейсу.
3) Arrow function (стрілочна функція)
const inc = x => x + 1;
const sum = (a, b) => a + b;- Лексичний
this: береться з зовнішнього лексичного оточення. - Немає
arguments(замість нього rest-параметри...args). - Не можна використовувати з
new, немаєprototype. - Чудово підходить для колбеків і коротких функцій.
Спеціальні різновиди: Function, генератори, async
4) Function constructor
const fn = new Function('a', 'b', 'return a + b');- Створює функцію з рядка, так само як
eval. - Не рекомендується: проблеми з безпекою і продуктивністю, плюс тіло компілюється в глобальній області видимості.
5) Generator function (function*)
function* idGenerator() {
let i = 0;
while (true) {
yield i++;
}
}- Повертає ітератор і підтримує
yield. - Виконання можна призупиняти і відновлювати.
6) Async function
async function fetchData() {
const r = await fetch('/api');
return r.json();
}
const fn = async () => { /* an async arrow works too */ };- Завжди повертає
Promise. - Усередині можна використовувати
await.
Комбінації теж припустимі: async function* це асинхронний генератор для for await...of.
Методи об'єктів і класів
7) Скорочений запис методів в об'єктах
const obj = {
sayHi() { console.log('hi'); }, // method
async load() { await fetch('/'); } // async method
};- Зручно для методів: вони мають
[[HomeObject]], тобто підтримкуsuper. - Можна писати і як
prop: function () {}, але це вже звичайний функціональний вираз без підтримкиsuper.
8) Методи класу (усередині class)
class C {
method() {}
static staticMethod() {}
async asyncMethod() {}
}- Оголошення класу не піднімається так, як function declaration: до виконання рядка з
classдоступу до нього немає. - Методи використовують з екземплярами або з самим класом і вони підтримують
super. - Тіло класу завжди виконується в строгому режимі.
Похідні форми: IIFE та bind
9) IIFE, Immediately Invoked Function Expression
(function () {
// runs immediately
})();- Корисно для створення локальної області видимості; до появи модулів це був основний спосіб не засмічувати глобальний scope.
10) Function.prototype.bind (створення зв'язаної функції)
function f() { console.log(this); }
const bound = f.bind({ x: 1 });- Повертає нову функцію з зафіксованим
thisта, за потреби, з частково застосованими аргументами. - Повторний
bindуже зв'язаної функції контекст не змінює.
Типові помилки
- Покладатися на hoisting для function expression:
mul(2, 3)до рядка зconst mul = function ...кинеReferenceError. - Оголошувати метод об'єкта стрілкою і чекати, що
thisвкаже на цей об'єкт. - Використовувати
new Functionчи рядок замість звичайної функції: це той самий ризик, що йeval. - Забувати, що
async-функція повертаєPromise, і читати її результат безawaitчи.then(). - Плутати
prop: function () {}зі скороченимprop() {}: перший варіант не має[[HomeObject]], томуsuperу ньому не працює. - Оголошувати function declaration усередині блока
ifі розраховувати на стабільну поведінку в різних середовищах: краще використати вираз.
Порівняння форм
| Форма | Hoisting | Власний this | new | prototype |
|---|---|---|---|---|
| Function declaration | Так | Так | Так | Так |
| Function expression | Ні | Так | Так | Так |
| Arrow function | Ні | Ні (лексичний) | Ні | Ні |
new Function | Ні | Так | Так | Так |
Generator function* | Так (для declaration) | Так | Ні | Так |
| Async function | Так (для declaration) | Так | Ні | Ні |
| Метод об'єкта або класу | Ні | Так | Ні | Ні |
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.