Втрата контексту
Чому втрачається this
this у JS залежить від способу виклику функції, а не від того, де вона оголошена.
Якщо ти передаєш метод як посилання, він уже не викликається "через об'єкт", і this стає undefined (або window у нестрогому режимі):
const user = {
name: 'Tim',
sayHi() {
console.log(`Привіт, я ${this.name}`);
}
};
setTimeout(user.sayHi, 1000); // "Привіт, я undefined"Чому так:
setTimeoutвикликає функцію якcallback(), а не якuser.sayHi(), томуthisвтрачає зв'язок з об'єктомuser.
Спосіб 1 - bind()
Метод Function.prototype.bind() створює нову функцію з жорстко прив'язаним контекстом.
setTimeout(user.sayHi.bind(user), 1000); // Привіт, я Timbind повертає нову функцію, в якій this завжди вказує на user, незалежно від способу виклику.
Спосіб 2 - стрілкові функції
Стрілкові функції не мають власного this,
а успадковують його із зовнішнього контексту (лексично).
const user = {
name: 'Tim',
sayHiLater() {
setTimeout(() => {
console.log(`Привіт, я ${this.name}`);
}, 1000);
}
};
user.sayHiLater(); // Привіт, я TimТут стрілкова функція "замкнула" this від sayHiLater.
Спосіб 3 - зберегти this у змінну
Класичний "старий" спосіб до появи стрілкових функцій.
const user = {
name: 'Tim',
sayHiLater() {
const self = this;
setTimeout(function() {
console.log(`Привіт, я ${self.name}`);
}, 1000);
}
};
user.sayHiLater(); // Привіт, я TimТут self (або that) зберігає посилання на контекст user.
Спосіб 4 - викликати функцію через call або apply
Можна вручну задати контекст при виклику.
function greet() {
console.log(`Привіт, ${this.name}`);
}
const user = { name: 'Tim' };
greet.call(user); // Привіт, Tim
greet.apply(user); // Привіт, TimЦе тимчасова прив'язка: працює тільки в момент виклику.
Спосіб 5 - використовувати клас (методи викликаються з екземпляром)
Всередині класів this зазвичай не втрачається, якщо ти викликаєш метод через екземпляр:
class User {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(`Привіт, ${this.name}`);
}
}
const tim = new User('Tim');
tim.sayHi(); // Привіт, TimАле якщо метод передати "у відриві", контекст знову втратиться:
const fn = tim.sayHi;
fn(); // undefinedЩоб уникнути цього, можна забіндити метод у конструкторі нижче.
Спосіб 6 - прив'язка в конструкторі (для класів і React)
class User {
constructor(name) {
this.name = name;
this.sayHi = this.sayHi.bind(this); // назавжди фіксуємо контекст
}
sayHi() {
console.log(`Привіт, ${this.name}`);
}
}
const tim = new User('Tim');
setTimeout(tim.sayHi, 1000); // Привіт, я TimЦе особливо корисно в React-класах (this.handleClick = this.handleClick.bind(this)).
Спосіб 7 - властивості-класи (class fields)
Сучасний спосіб: методи, оголошені як стрілкові функції всередині класу, автоматично успадковують this.
class User {
name = 'Tim';
sayHi = () => {
console.log(`Привіт, ${this.name}`);
};
}
const tim = new User();
setTimeout(tim.sayHi, 1000); // Привіт, TimЦе синтаксис ES2022 (підтримується в усіх сучасних браузерах).
Спосіб 8 - не передавати метод напряму
Іноді простіше просто викликати метод всередині анонімної обгортки,
щоб не втратити this:
setTimeout(() => user.sayHi(), 1000); // Привіт, я TimТут user.sayHi() викликається з правильним контекстом.
Підсумок
| Метод | Що робить | Де часто застосовують |
|---|---|---|
.bind(this) | Створює функцію з фіксованим контекстом | Колбеки, React, таймери |
| Стрілкова функція | Успадковує this із зовнішньої області | setTimeout, async, проміси |
self = this | Старий спосіб зберегти посилання | Старий код, колбеки |
call / apply | Тимчасове встановлення контексту | Одноразові виклики |
| Прив'язка в класі | Через bind у constructor або стрілку | React-класи, ООП |
| Анонімна обгортка | Викликає метод у контексті | Прості колбеки |
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.