call, apply та bind.Функції є “робочими конячками” будь-якої програми. Вони дозволяють ізолювати шматок коду, дати йому ім’я та викликати його у потрібний момент скільки завгодно разів, замість того, щоб копіювати та вставляти однакові рядки. Це принцип DRY (Don’t Repeat Yourself).
Однак у JavaScript функції — це набагато більше, ніж просто підпрограми. Вони є об’єктами першого класу (First-class citizens). Це означає, що функцію можна зберегти у змінну, передати іншій функції як аргумент (породивши Callback) або навіть повернути з іншої функції як результат (породивши Замикання). Ця особливість робить JS надзвичайно гнучкою мовою програмування, стираючи межі між даними та поведінкою.
У цій лекції ми розберемо еволюцію синтаксису функцій (від Function Declaration до сучасних стрілочних функцій ES6), дослідимо магію Замикань, яка часто є найскладнішою темою на співбесідах, та остаточно усунемо плутанину з ключовим словом this.
Якщо функція в JS є об’єктом, то ми можемо робити з нею все те саме, що і зі звичайними змінними:
myFunction.count = 0).// Функтив як змінна (Callback)
function greet(name) {
console.log(`Привіт, ${name}!`);
}
function processUserInput(callback) {
const name = prompt("Будь ласка, введіть своє ім'я.");
callback(name); // Викликаємо функцію, яку нам передали
}
// Передаємо функцію greet як аргумент (БЕЗ ДУЖОК!)
processUserInput(greet);
У JS існує три основних способи створення функцій, кожен з яких має свої нюанси.
Це “класичний” спосіб. Головна особливість — Hoisting (Підняття). JS-рушій “знаходить” ці функції ще до початку виконання коду. Тому функцію можна викликати до місця, де вона написана в коді.
sayHi(); // Працює ідеально, хоча функція написана нижче
function sayHi() {
console.log("Привіт!");
}
Функція створюється і відразу присвоюється змінній (const або let). Такі функції не піднімаються. Виклик такої функції до її оголошення призведе до помилки (ReferenceError). Вони часто бувають анонімними (не мають імені після ключового слова function).
// sayHello(); // ОШИБКА! Cannot access 'sayHello' before initialization
const sayHello = function () {
console.log("Hello!");
};
sayHello(); // Працює
Це найсучасніший, найкоротший і зараз найпопулярніший спосіб. Використовує “товсту стрілку” (=>).
Стрілочні функції є анонімними (тому їх завжди присвоюють у змінну або передають як callback).
Синтаксис:
// Звичайна функція
const add = function (a, b) {
return a + b;
};
// Стрілочна функція (абсолютний аналог)
const addArrow = (a, b) => {
return a + b;
};
// Найкоротший синтаксис (Явного return немає, він відбувається АВТОМАТИЧНО)
// Працює тільки якщо тіло функції складається з ОДНОГО виразу
const addShort = (a, b) => a + b;
// Якщо параметр лише один, дужки можна опустити
const double = (x) => x * 2;
Увага: Крім короткого синтаксису, стрілочні функції мають принципову відмінність у роботі з ключовим словом
this(див. розділ 6).
Параметри за замовчуванням (Default Parameters):
Дозволяють функції мати значення “про всяк випадок”, якщо при виклику цей аргумент забудуть передати (щоб він не дорівнював undefined).
function greetUser(name = "Гість", role = "Користувач") {
console.log(`Привіт, ${name}. Твоя роль: ${role}`);
}
greetUser("Іван"); // Привіт, Іван. Твоя роль: Користувач
greetUser(); // Привіт, Гість. Твоя роль: Користувач
Оператор Rest (...):
Виглядає так само, як Spread оператор (три крапки), але робить протилежне. Якщо Spread “розсипає” масив, то Rest (залишок) — “збирає” всі передані аргументи в один масив. Використовується в параметрах функції, коли ми не знаємо, скільки точно аргументів нам передадуть.
function sum(...numbers) {
// Тепер numbers — це справжній масив усіх переданих чисел [1, 2, 3, 4]
let total = 0;
for (let num of numbers) {
total += num;
}
return total;
}
console.log(sum(1, 2)); // 3
console.log(sum(1, 2, 3, 4)); // 10
Будь-яка змінна у JS існує в певному середовищі — області видимості.
let, const): Змінна заперта всередині фігурних дужок { ... } (в циклі for, в if, всередині функції). Зовні її не видно.var): Заперта лише всередині функції, ігнорує звичайні блоки if або for. Це причина не використовувати var.Лексичне оточення (Lexical Environment): Коли створюється функція, вона запам’ятовує, де (в якому місці коду) вона народилася. Лексичне оточення — це прихований об’єкт, який створюється при виклику функції. Він містить:
Правило пошуку: Якщо скрипт не знаходить змінну всередині функції, він “йде наверх” — шукає її в зовнішньому оточенні, і так аж до самого глобального об’єкта window. Це називається “Ланцюгом областей видимості” (Scope Chain).
Замикання — це здатність функції запам’ятовувати своє зовнішнє лексичне оточення і мати доступ до нього, навіть коли функція викликається ПОЗА цим оточенням.
Замикання виникає щоразу, коли функція створюється всередині іншої функції і повертається з неї.
function createCounter() {
let count = 0; // Локальна змінна батьківської функції
// Повертаємо внутрішню функцію як результат
return function increment() {
count++; // Внутрішня функція використовує count із зовнішньої функції
console.log(count);
};
}
const myCounter = createCounter(); // createCounter відпрацювала і завершилась
const counter2 = createCounter(); // Створили зовсім незалежний лічильник
// Але внутрішня функція (в myCounter) ЗАПАМ'ЯТАЛА змінну count!
myCounter(); // 1
myCounter(); // 2
myCounter(); // 3
counter2(); // 1 (він має своє особисте замикання)
Навіщо потрібні замикання?
count у фрагменті коду вище. Жодним способом ззовні програми ми не можемо напряму змінити count (наприклад count = 100). Єдиний спосіб впливати на неї — викликати нашу функцію myCounter(). Це основа створення “приватних” змінних у JS.Ключове слово this (Цей) — найзаплутаніша концепція в JS для початківців.
Загальне правило: this посилається на об’єкт, який зараз викликає функцію. Значення this обчислюється не там, де функція написана, а В МОМЕНТ її виклику.
Ситуація 1: Метод об’єкта
Якщо функція викликається як метод (через крапку: object.method()), то this — це об’єкт перед крапкою.
const user = {
name: "Петро",
sayHi() {
console.log(`Привіт, я ${this.name}`); // this вказує на об'єкт user
},
};
user.sayHi(); // Викличе "Привіт, я Петро"
Ситуація 2: Простий виклик функції
Якщо ви просто викликаєте звичайну функцію (без крапки), this вказує на глобальний об’єкт (в браузері це window). Якщо увімкнено суворий режим ("use strict";), то this буде дорівнювати undefined.
Ситуація 3: Стрілочні функції (Arrow Functions)
Стрілочні функції не мають власного this. Вони беруть (запозичують) this з навколишнього середовища (з того контексту, де вони були НАПИСАНІ). Це їхня головна відмінність.
Коли це корисно? Коли ми створюємо callback-функції всередині методів об’єкта. Звичайна функція “загубила” б контекст, а стрілочна його утримає.
const group = {
title: "Група 101",
students: ["Іван", "Марія"],
showList() {
// Використовуємо стрілочну функцію у forEach
// Вона не має свого this, тому бере this батька (методу showList), тобто об'єкт group.
this.students.forEach((student) => {
console.log(`${this.title}: ${student}`);
});
},
};
group.showList();
// Група 101: Іван
// Група 101: Марія
// Якби у forEach була звичайна function(student){...} - ми б отримали undefined: Іван
Хінт: Ніколи не використовуйте стрілочні функції як методи об’єкта!
Іноді нам потрібно взяти метод з одного об’єкта і застосувати до іншого, примусово вказавши, чим має бути this. Для цього у кожної звичайної функції є вбудовані методи.
call(context, arg1, arg2...): Викликає функцію негайно, підміняючи this на context, а аргументи передаються через кому.apply(context, [array_of_args]): Робить те саме, що й call, але приймає аргументи як один єдиний масив.bind(context, arg1...): Вона не викликає функцію негайно. Вона повертає нову функцію, яка назавжди “прив’язана” до вказаного this. Стандартно використовується в React для методів класів.const animal = { sound: "Роар!" };
function speak(msg) {
console.log(`${msg} Каже: ${this.sound}`);
}
// Примусово вказуємо, що this має дорівнювати об'єкту animal
speak.call(animal, "Увага!"); // Увага! Каже: Роар!
// Створюємо нову функцію, намертво прив'язану до animal
const boundSpeak = speak.bind(animal);
boundSpeak("Привіт!"); // Привіт! Каже: Роар!
() => {}) надають короткий синтаксис та не створюють власний контекст this, що рятує при використанні Callbacks....) дозволяє елегантно збирати змінну кількість аргументів функції в єдиний масив.this: у звичайній функції this визначається тим, ЯК функція була викликана (об’єкт перед крапкою). У стрілочній функції this береться з того місця, ДЕ функція була написана (Лексичний this).call, apply та bind дозволяють програмісту стати диктатором і явно вказати функції, що саме має бути її контекстом this....?console.log(this) в глобальній області видимості в браузері? А всередині методу об’єкта?user.showName(), і ви передасте його як callback (наприклад у таймер: setTimeout(user.showName, 1000)), швидше за все контекст загубиться. Як вижити в цій ситуації за допомогою методів bind або стрілочної функції?call() та bind()?