Пошук уроків, статей та іншого контенту
Чотири способи, якими викликається функція, визначають значення this — і чому стрілкові функції інакші.
На відміну від більшості мов, де this (чи self) завжди посилається на екземпляр класу, у JavaScript значення this визначається не там, де функцію написано, а тим, як саме її викликали. Та сама функція може мати різні значення this залежно від способу виклику — це і є джерело плутанини, поки не вивчити чіткі правила.
Коли функцію викликають через крапку (obj.method()), this всередині дорівнює об'єкту зліва від крапки:
const user = {
name: "Олена",
greet() {
return `Привіт, я ${this.name}`;
},
};
user.greet(); // "Привіт, я Олена" — this === userКоли функцію викликають саму по собі, без об'єкта зліва, this у strict mode (а всі модулі за замовчуванням у strict mode — §0 в попередньому уроці цього модуля) дорівнює undefined:
function showThis() {
console.log(this);
}
showThis(); // undefined (у strict mode / ES-модулях)Найпоширеніша пастка: якщо витягнути метод з об'єкта в окрему змінну, зв'язок із об'єктом втрачається — при виклику він поводиться за правилом 2, а не правилом 1:
const greetFn = user.greet;
greetFn(); // TypeError: this.name — this тепер undefined, а не userТри методи дозволяють явно вказати, чим буде this під час виклику:
function greet() {
return `Привіт, я ${this.name}`;
}
const user = { name: "Максим" };
greet.call(user); // "Привіт, я Максим" — викликає одразу з заданим this
greet.apply(user); // те саме, різниця лише у форматі передачі аргументів (масив замість переліку)
const boundGreet = greet.bind(user);
boundGreet(); // "Привіт, я Максим" — bind повертає НОВУ функцію з "прив'язаним" this назавждиbind — саме той інструмент, що вирішує пастку з правила 2: якщо метод потрібно передати як колбек (наприклад, обробник події) і зберегти прив'язку до об'єкта, obj.method.bind(obj) гарантує правильний this незалежно від того, як його зрештою викличуть.
Коли функцію викликають з new, JavaScript створює новий порожній об'єкт і встановлює this на нього:
function User(name) {
this.name = name; // this — новостворений об'єкт
}
const user = new User("Ірина");
user.name; // "Ірина"Ми згадували це в модулі «Функції»: стрілкові функції не мають власного this — вони беруть його лексично з навколишнього коду, за тим самим принципом, що й область видимості. Жодне з чотирьох правил вище до них не застосовується:
const counter = {
count: 0,
increment: () => {
this.count++; // this НЕ counter — стрілкова функція взяла this з навколишньої області (де counter створювався), не з виклику
},
};Це якраз робить стрілкові функції ідеальними для колбеків усередині методів, де потрібен this зовнішнього методу:
const timer = {
seconds: 0,
start() {
setInterval(() => {
this.seconds++; // стрілкова функція успадковує this від start() — тобто timer
console.log(this.seconds);
}, 1000);
},
};
timer.start(); // 1, 2, 3... — this.seconds коректно оновлює timer.secondsЯкби колбек у setInterval був звичайною функцією (не стрілковою), його this визначався б правилом 2 (звичайний виклик) — undefined, і this.seconds++ кинув би помилку. Саме тому стрілкові функції стали ідіоматичним вибором для колбеків усередині методів.
Коли застосовуються кілька правил одночасно, пріоритет такий: new > явне call/apply/bind > виклик як метод > звичайний виклик. Стрілкові функції стоять окремо — вони завжди ігнорують усі чотири правила на користь лексичного this.
Передача методу об'єкта як колбека без bind — this губиться (правило 2 замість правила 1).
Використання стрілкової функції для методу об'єкта (як у прикладі з counter вище) — вона не отримає this об'єкта.
Забування, що this у звичайній функції поза строгим режимом (не в модулях) посилається на глобальний об'єкт, а не на undefined — джерело різної поведінки залежно від середовища виконання.
this визначається способом виклику функції: як метод (this — об'єкт зліва від крапки), окремо (this — undefined у strict mode), явно через call/apply/bind, або через new (this — новий об'єкт). Стрілкові функції — виняток: вони не мають власного this і завжди беруть його з навколишнього лексичного контексту, що робить їх зручними саме для колбеків усередині методів.
Спробуйте самостійно
Візьміть об'єкт { name: "Тест", greet() { return this.name; } }, витягніть greet в окрему змінну і викличте — переконайтесь, що this загубився. Потім виправте це через bind.