Skip to main content

Втрата контексту

Чому втрачається this

this у JS залежить від способу виклику функції, а не від того, де вона оголошена. Якщо ти передаєш метод як посилання, він уже не викликається "через об'єкт", і this стає undefined (або window у нестрогому режимі):

javascript
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() створює нову функцію з жорстко прив'язаним контекстом.

javascript
setTimeout(user.sayHi.bind(user), 1000); // Привіт, я Tim

bind повертає нову функцію, в якій this завжди вказує на user, незалежно від способу виклику.


Спосіб 2 - стрілкові функції

Стрілкові функції не мають власного this, а успадковують його із зовнішнього контексту (лексично).

javascript
const user = { name: 'Tim', sayHiLater() { setTimeout(() => { console.log(`Привіт, я ${this.name}`); }, 1000); } }; user.sayHiLater(); // Привіт, я Tim

Тут стрілкова функція "замкнула" this від sayHiLater.


Спосіб 3 - зберегти this у змінну

Класичний "старий" спосіб до появи стрілкових функцій.

javascript
const user = { name: 'Tim', sayHiLater() { const self = this; setTimeout(function() { console.log(`Привіт, я ${self.name}`); }, 1000); } }; user.sayHiLater(); // Привіт, я Tim

Тут self (або that) зберігає посилання на контекст user.


Спосіб 4 - викликати функцію через call або apply

Можна вручну задати контекст при виклику.

javascript
function greet() { console.log(`Привіт, ${this.name}`); } const user = { name: 'Tim' }; greet.call(user); // Привіт, Tim greet.apply(user); // Привіт, Tim

Це тимчасова прив'язка: працює тільки в момент виклику.


Спосіб 5 - використовувати клас (методи викликаються з екземпляром)

Всередині класів this зазвичай не втрачається, якщо ти викликаєш метод через екземпляр:

javascript
class User { constructor(name) { this.name = name; } sayHi() { console.log(`Привіт, ${this.name}`); } } const tim = new User('Tim'); tim.sayHi(); // Привіт, Tim

Але якщо метод передати "у відриві", контекст знову втратиться:

javascript
const fn = tim.sayHi; fn(); // undefined

Щоб уникнути цього, можна забіндити метод у конструкторі нижче.


Спосіб 6 - прив'язка в конструкторі (для класів і React)

javascript
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.

javascript
class User { name = 'Tim'; sayHi = () => { console.log(`Привіт, ${this.name}`); }; } const tim = new User(); setTimeout(tim.sayHi, 1000); // Привіт, Tim

Це синтаксис ES2022 (підтримується в усіх сучасних браузерах).


Спосіб 8 - не передавати метод напряму

Іноді простіше просто викликати метод всередині анонімної обгортки, щоб не втратити this:

javascript
setTimeout(() => user.sayHi(), 1000); // Привіт, я Tim

Тут user.sayHi() викликається з правильним контекстом.


Підсумок

МетодЩо робитьДе часто застосовують
.bind(this)Створює функцію з фіксованим контекстомКолбеки, React, таймери
Стрілкова функціяУспадковує this із зовнішньої областіsetTimeout, async, проміси
self = thisСтарий спосіб зберегти посиланняСтарий код, колбеки
call / applyТимчасове встановлення контекстуОдноразові виклики
Прив'язка в класіЧерез bind у constructor або стрілкуReact-класи, ООП
Анонімна обгорткаВикликає метод у контекстіПрості колбеки

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.