Пошук уроків, статей та іншого контенту
Дізнайтеся, чому функції в JavaScript є значеннями першого класу: їх можна зберігати у змінних, об'єктах і структурах даних, а також передавати та повертати як звичайні значення.
У JavaScript функції — це значення першого класу. Це означає, що з функціями можна працювати майже так само, як із числами, рядками чи об’єктами:
зберігати у змінній;
передавати в іншу функцію;
повертати з функції;
зберігати в масивах та об’єктах;
присвоювати властивостям об’єктів;
створювати під час виконання програми.
function greet(name) {
return `Привіт, ${name}!`;
}
const message = greet("Олено");
console.log(message); // Привіт, Олено!У цьому прикладі greet — це ім’я, яке посилається на функцію. Але саму функцію можна присвоїти іншій змінній:
function greet(name) {
return `Привіт, ${name}!`;
}
const sayHello = greet;
console.log(sayHello("Андрію")); // Привіт, Андрію!
console.log(greet === sayHello); // truegreet і sayHello посилаються на один і той самий об’єкт-функцію. Створення sayHello не копіює функцію, а створює ще одне посилання на неї.
Функцію можна створити за допомогою функціонального виразу й зберегти у змінній:
const add = function (firstNumber, secondNumber) {
return firstNumber + secondNumber;
};
console.log(add(2, 3)); // 5Функціональний вираз — це вираз, результатом якого є функція.
Таку функцію можна записати зі стрілковим синтаксисом:
const multiply = (firstNumber, secondNumber) => {
return firstNumber * secondNumber;
};
console.log(multiply(4, 5)); // 20Якщо тіло стрілкової функції складається лише з одного виразу, return можна не писати:
const square = number => number * number;
console.log(square(6)); // 36Ці два способи створення функції мають важливу відмінність:
console.log(declaredFunction()); // Працює
function declaredFunction() {
return "Функцію оголошено";
}Оголошення функції доступне до місця, де воно записане, через механізм підняття оголошень.
Функціональний вираз не можна викликати до ініціалізації змінної:
// Виклик до цього місця спричинив би ReferenceError
const expressionFunction = function () {
return "Функціональний вираз";
};
console.log(expressionFunction()); // Функціональний виразНа практиці функціональні вирази часто використовують для передавання функцій і створення локальної поведінки.
Оскільки функція є значенням, її можна передати іншій функції як аргумент.
function processNumber(number, operation) {
return operation(number);
}
function double(number) {
return number * 2;
}
function makeNegative(number) {
return -number;
}
console.log(processNumber(7, double)); // 14
console.log(processNumber(7, makeNegative)); // -7Функція processNumber не знає, яку саме операцію потрібно виконати. Вона отримує цю операцію через параметр operation.
Такі функції називають функціями вищого порядку, якщо вони:
приймають іншу функцію як аргумент;
повертають функцію;
або роблять і те, і інше.
Функцію, яку передають іншій функції для виклику пізніше, називають callback-функцією або функцією зворотного виклику.
function repeatThreeTimes(callback) {
for (let index = 0; index < 3; index += 1) {
callback(index);
}
}
repeatThreeTimes(function (index) {
console.log(`Ітерація №${index + 1}`);
});Результат:
Ітерація №1
Ітерація №2
Ітерація №3Часто callback записують як стрілкову функцію:
repeatThreeTimes(index => {
console.log(`Поточний індекс: ${index}`);
});Методи масивів активно використовують функції як аргументи.
mapmap викликає передану функцію для кожного елемента та створює новий масив із результатів:
const prices = [100, 250, 80];
const pricesWithTax = prices.map(price => price * 1.2);
console.log(pricesWithTax); // [120, 300, 96]
console.log(prices); // [100, 250, 80]Callback для map отримує поточний елемент, його індекс і весь масив:
const names = ["Олена", "Андрій", "Марія"];
const labels = names.map((name, index) => {
return `${index + 1}. ${name}`;
});
console.log(labels);
// ["1. Олена", "2. Андрій", "3. Марія"]filterfilter створює новий масив лише з елементів, для яких callback повернув true:
const numbers = [3, 8, 12, 5, 20];
const evenNumbers = numbers.filter(number => number % 2 === 0);
console.log(evenNumbers); // [8, 12, 20]reducereduce послідовно обробляє елементи та зводить масив до одного значення:
const expenses = [120, 80, 250];
const total = expenses.reduce((sum, expense) => {
return sum + expense;
}, 0);
console.log(total); // 450У цьому прикладі:
sum — проміжний результат;
expense — поточний елемент;
0 — початкове значення.
Функції можна зберігати в масивах і викликати за індексом:
const operations = [
number => number + 10,
number => number * 2,
number => number ** 2
];
console.log(operations[0](5)); // 15
console.log(operations[1](5)); // 10
console.log(operations[2](5)); // 25Це зручно, коли набір доступних дій формується динамічно.
Наприклад, можна послідовно застосувати кілька перетворень:
const transformations = [
value => value.trim(),
value => value.toLowerCase(),
value => value.replaceAll(" ", "-")
];
let slug = " JavaScript Functions ";
for (const transform of transformations) {
slug = transform(slug);
}
console.log(slug); // javascript-functionsФункції можна використовувати як значення властивостей об’єкта:
const calculator = {
add(firstNumber, secondNumber) {
return firstNumber + secondNumber;
},
subtract(firstNumber, secondNumber) {
return firstNumber - secondNumber;
},
multiply(firstNumber, secondNumber) {
return firstNumber * secondNumber;
}
};
console.log(calculator.add(10, 3)); // 13
console.log(calculator.multiply(10, 3)); // 30Функція, яка є властивістю об’єкта, називається методом цього об’єкта.
Об’єкт можна використовувати як таблицю вибору операції:
const operations = {
add: (firstNumber, secondNumber) => firstNumber + secondNumber,
subtract: (firstNumber, secondNumber) => firstNumber - secondNumber,
multiply: (firstNumber, secondNumber) => firstNumber * secondNumber
};
function calculate(operationName, firstNumber, secondNumber) {
const operation = operations[operationName];
if (typeof operation !== "function") {
throw new Error(`Невідома операція: ${operationName}`);
}
return operation(firstNumber, secondNumber);
}
console.log(calculate("add", 8, 2)); // 10
console.log(calculate("multiply", 8, 2)); // 16Такий підхід допомагає уникати довгих ланцюжків if...else або switch, коли поведінка залежить від ключа.
Функція може повертати іншу функцію:
function createMultiplier(multiplier) {
return function (number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(10)); // 20
console.log(triple(10)); // 30createMultiplier(2) повертає функцію, яка множить число на 2. Ця повернена функція зберігається у змінній double.
Те саме можна записати зі стрілковими функціями:
const createMultiplier = multiplier => number => number * multiplier;
const quadruple = createMultiplier(4);
console.log(quadruple(5)); // 20У попередньому прикладі повернена функція використовує змінну multiplier, хоча зовнішня функція вже завершила роботу. Це називається замиканням.
Замикання — це функція разом із доступом до змінних із зовнішнього лексичного оточення.
function createCounter(initialValue = 0) {
let count = initialValue;
return function () {
count += 1;
return count;
};
}
const counter = createCounter(10);
console.log(counter()); // 11
console.log(counter()); // 12
console.log(counter()); // 13Змінна count недоступна безпосередньо ззовні, але повернена функція може читати та змінювати її.
Замикання корисні для:
приховування внутрішнього стану;
створення фабрик функцій;
обробників подій;
реалізації кешування;
налаштування поведінки функцій.
Важливо розрізняти передавання функції та її виклик:
function showMessage() {
console.log("Повідомлення");
}
function execute(callback) {
callback();
}
execute(showMessage); // Передали функцію
execute(showMessage()); // Спочатку викликали функціюУ другому випадку showMessage() виконується одразу. Оскільки вона нічого не повертає, у execute передається значення undefined.
Зазвичай callback передають без круглих дужок:
setTimeout(showMessage, 1000);Якщо потрібно передати аргументи, можна використати обгортку:
setTimeout(() => {
showMessage();
}, 1000);thisФункції є значеннями, але спосіб їх виклику може впливати на значення this.
const user = {
name: "Олена",
greet() {
return `Привіт, я ${this.name}`;
}
};
console.log(user.greet()); // Привіт, я ОленаЯкщо зберегти метод в окрему змінну, зв’язок з об’єктом не зберігається автоматично:
const user = {
name: "Олена",
greet() {
return `Привіт, я ${this.name}`;
}
};
const greetUser = user.greet;
// У строгому режимі this буде undefined,
// тому такий виклик не використовуйте без прив'язки контексту.Для збереження контексту можна використати bind:
const user = {
name: "Олена",
greet() {
return `Привіт, я ${this.name}`;
}
};
const greetUser = user.greet.bind(user);
console.log(greetUser()); // Привіт, я Оленаbind повертає нову функцію, у якої this назавжди прив’язаний до переданого об’єкта.
Функції першого класу дозволяють створювати універсальні інструменти. Наприклад, функцію, яка послідовно застосовує кілька перетворень:
function pipe(value, ...functions) {
return functions.reduce((currentValue, currentFunction) => {
return currentFunction(currentValue);
}, value);
}
const result = pipe(
" JavaScript Functions ",
text => text.trim(),
text => text.toLowerCase(),
text => text.replaceAll(" ", "-")
);
console.log(result); // javascript-functionspipe приймає:
початкове значення;
довільну кількість функцій;
передає результат кожної функції до наступної.
Такий підхід допомагає розділяти складну обробку на невеликі незалежні кроки.
Для перевірки, чи є значення функцією, використовують оператор typeof:
function isFunction(value) {
return typeof value === "function";
}
console.log(isFunction(() => {})); // true
console.log(isFunction("text")); // false
console.log(isFunction(42)); // false
console.log(isFunction(null)); // falseЦе корисно, коли функція надходить як параметр і потрібно перевірити коректність аргументів:
function runCallback(callback) {
if (typeof callback !== "function") {
throw new TypeError("Потрібно передати функцію");
}
return callback();
}
console.log(runCallback(() => "Готово")); // ГотовоНеправильно:
function execute(callback) {
callback();
}
function getMessage() {
return "Готово";
}
// execute(getMessage()) передасть рядок, а не функціюПравильно:
execute(getMessage);Якщо потрібно передати результат виклику з аргументом:
execute(() => getMessage("текст"));map змінить початковий масивmap повертає новий масив:
const numbers = [1, 2, 3];
const doubled = numbers.map(number => number * 2);
console.log(numbers); // [1, 2, 3]
console.log(doubled); // [2, 4, 6]Якщо результат не зберегти, перетворений масив буде втрачено.
return у callbackconst numbers = [1, 2, 3];
const doubled = numbers.map(number => {
number * 2;
});
console.log(doubled); // [undefined, undefined, undefined]Якщо використовується блок {}, потрібно явно повернути результат:
const doubled = numbers.map(number => {
return number * 2;
});Або скористатися неявним поверненням:
const doubled = numbers.map(number => number * 2);thisЯкщо метод передають окремо, він може втратити об’єкт, з яким був викликаний:
const object = {
value: 10,
getValue() {
return this.value;
}
};
const getValue = object.getValue;Для таких випадків використовуйте bind, звичайну функцію-обгортку або стрілкову функцію в місці створення callback.
Надмірна залежність від зовнішніх змінних ускладнює розуміння коду:
let total = 0;
[10, 20, 30].forEach(value => {
total += value;
});У простих випадках краще повернути результат через reduce:
const total = [10, 20, 30].reduce((sum, value) => {
return sum + value;
}, 0);Це не означає, що змінювати стан заборонено. Важливо розуміти, де така зміна справді потрібна, а де її можна уникнути.
У JavaScript функції є значеннями першого класу.
Функції можна зберігати у змінних, масивах та об’єктах.
Функції можна передавати як аргументи та повертати з інших функцій.
Callback — це функція, передана для виклику пізніше.
Функції вищого порядку приймають функції, повертають їх або роблять і те, і інше.
Методи map, filter і reduce активно використовують callback-функції.
Повернені функції можуть зберігати доступ до зовнішніх змінних завдяки замиканням.
Потрібно розрізняти передавання функції та її виклик.
Під час передавання методів важливо враховувати контекст this.
Функції першого класу допомагають створювати гнучкий, повторно використовуваний і декларативний код.