Пошук уроків, статей та іншого контенту
Навчитеся працювати з this у методах класів, статичних методах і гетерах та сетерах.
this у класахУ JavaScript this — це спеціальне значення, яке посилається на об’єкт, пов’язаний із поточним викликом функції.
У методах класу this зазвичай посилається на екземпляр класу, для якого викликано метод:
class User {
constructor(name) {
this.name = name;
}
sayHello() {
return `Привіт, мене звати ${this.name}`;
}
}
const user = new User("Олена");
console.log(user.sayHello());
// Привіт, мене звати ОленаПід час виклику user.sayHello():
JavaScript знаходить метод sayHello.
Викликає його в контексті об’єкта user.
Усередині методу this посилається на user.
Тому this.name фактично означає user.name.
this у конструкторіКонструктор виконується під час створення нового екземпляра за допомогою new.
У конструкторі this посилається на новий об’єкт:
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
}
const product = new Product("Книга", 300);
console.log(product.name); // Книга
console.log(product.price); // 300Кожен екземпляр класу має власні значення властивостей:
const firstProduct = new Product("Книга", 300);
const secondProduct = new Product("Рюкзак", 1200);
console.log(firstProduct.name); // Книга
console.log(secondProduct.name); // Рюкзакthis.name у конструкторі не є глобальною змінною. Це властивість конкретного об’єкта, який зараз створюється.
Методи, оголошені в класі, належать прототипу класу. У звичайному виклику через екземпляр this посилається на цей екземпляр:
class Counter {
constructor() {
this.value = 0;
}
increment() {
this.value += 1;
}
decrement() {
this.value -= 1;
}
getValue() {
return this.value;
}
}
const counter = new Counter();
counter.increment();
counter.increment();
console.log(counter.getValue()); // 2
counter.decrement();
console.log(counter.getValue()); // 1Методи можуть змінювати властивості екземпляра або повертати їхні значення.
Значення this залежить від способу виклику, а не лише від місця оголошення методу.
class User {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
const user = new User("Марія");
console.log(user.getName()); // Марія
const getName = user.getName;
console.log(getName()); // Помилка або undefinedУ другому випадку метод викликається як звичайна функція, а не як user.getName().
Метод втрачає контекст об’єкта. У класах методи працюють у строгому режимі, тому в такій ситуації this матиме значення undefined.
thisЄ кілька способів не втратити this.
bindbind створює нову функцію, у якій this назавжди прив’язаний до вказаного об’єкта:
class User {
constructor(name) {
this.name = name;
this.getName = this.getName.bind(this);
}
getName() {
return this.name;
}
}
const user = new User("Марія");
const getName = user.getName;
console.log(getName()); // МаріяУ конструкторі this.getName.bind(this) створює прив’язану версію методу.
Цей підхід часто використовують, коли метод передають як callback:
class Timer {
constructor() {
this.seconds = 0;
this.tick = this.tick.bind(this);
}
tick() {
this.seconds += 1;
console.log(this.seconds);
}
}
const timer = new Timer();
setTimeout(timer.tick, 1000);Через bind метод збереже посилання на екземпляр timer.
Стрілкові функції не створюють власного this. Вони використовують this із зовнішнього контексту.
Це зручно для callback-функцій усередині методів:
class User {
constructor(name) {
this.name = name;
}
printMessages(messages) {
messages.forEach((message) => {
console.log(`${this.name}: ${message}`);
});
}
}
const user = new User("Олена");
user.printMessages(["Привіт", "Як справи?"]);
// Олена: Привіт
// Олена: Як справи?Стрілкова функція всередині printMessages використовує this самого методу printMessages.
Звичайна функція мала б окремий контекст:
class User {
constructor(name) {
this.name = name;
}
printMessages(messages) {
messages.forEach(function (message) {
console.log(`${this.name}: ${message}`);
});
}
}
const user = new User("Олена");
user.printMessages(["Привіт"]);
// Помилка або undefined, залежно від середовищаУ такому випадку function (message) { ... } має власний this, який не посилається на user.
Розглянемо приклад із setTimeout:
class Notification {
constructor(message) {
this.message = message;
}
show() {
console.log(this.message);
}
}
const notification = new Notification("Нове повідомлення");
setTimeout(notification.show, 1000);Тут show передано як функцію, тому під час виконання вона не викликається як notification.show(). Контекст втрачається.
Виправлення за допомогою стрілкової функції:
setTimeout(() => {
notification.show();
}, 1000);Або за допомогою bind:
setTimeout(notification.show.bind(notification), 1000);Стрілкова функція часто є зрозумілішим варіантом, якщо callback потрібен лише в одному місці.
Статичний метод належить самому класу, а не його екземплярам. Він оголошується з ключовим словом static.
class MathHelper {
static double(value) {
return value * 2;
}
}
console.log(MathHelper.double(5)); // 10Статичний метод викликають через ім’я класу:
MathHelper.double(5);Викликати його через екземпляр не можна:
const helper = new MathHelper();
console.log(helper.double(5));
// TypeError: helper.double is not a functionthis у статичному методіУ статичному методі this посилається на клас, через який метод викликали:
class User {
static createGuest() {
return new this("Гість");
}
constructor(name) {
this.name = name;
}
}
const guest = User.createGuest();
console.log(guest.name); // ГістьУ цьому прикладі:
this усередині createGuest — це клас User;
new this("Гість") створює новий екземпляр цього класу.
Статичні методи зручно використовувати для фабричних методів, перевірки даних або операцій, які не залежать від конкретного екземпляра.
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
static isValidEmail(email) {
return email.includes("@");
}
}
console.log(User.isValidEmail("user@example.com")); // true
console.log(User.isValidEmail("invalid-email")); // falseЦей метод не потребує this екземпляра, тому він є статичним.
Гетер — це спеціальний метод, який дає змогу отримувати значення властивості як звичайну властивість, без круглих дужок.
Для оголошення гетера використовують get:
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
get area() {
return this.width * this.height;
}
}
const rectangle = new Rectangle(5, 3);
console.log(rectangle.area); // 15Зверніть увагу: гетер викликається без ():
rectangle.area;А не так:
rectangle.area();Гетер може обчислювати значення на основі інших властивостей:
class User {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
const user = new User("Олена", "Коваль");
console.log(user.fullName); // Олена КовальГетер не обов’язково зберігає значення. Він може обчислювати його щоразу під час звернення:
class Cart {
constructor() {
this.items = [];
}
addItem(price) {
this.items.push(price);
}
get total() {
return this.items.reduce((sum, price) => sum + price, 0);
}
}
const cart = new Cart();
cart.addItem(100);
cart.addItem(250);
console.log(cart.total); // 350Сетер — це спеціальний метод, який виконується під час присвоєння значення властивості.
Для оголошення сетера використовують set:
class User {
constructor(name) {
this.name = name;
}
set username(value) {
this.name = value.trim();
}
}
const user = new User("Олена");
user.username = " Марія ";
console.log(user.name); // МаріяСетер викликається як звичайне присвоєння:
user.username = "Марія";А не як функція:
user.username("Марія");Сетери зручно використовувати для перевірки або нормалізації даних:
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
set price(value) {
if (typeof value !== "number" || value < 0) {
throw new Error("Ціна має бути невід’ємним числом");
}
this._price = value;
}
get price() {
return this._price;
}
}
const product = new Product("Книга", 300);
console.log(product.price); // 300
product.price = 350;
console.log(product.price); // 350Властивість _price часто використовують як внутрішнє сховище для значення. Символ _ не робить властивість приватною автоматично. Це лише загальноприйнята домовленість: властивість призначена для внутрішнього використання.
Не можна в гетері звертатися до тієї самої властивості, для якої він оголошений:
class User {
get name() {
return this.name;
}
}Звернення до this.name знову викличе гетер name, який знову звернеться до this.name. Це спричинить нескінченну рекурсію.
Так само не можна в сетері присвоювати значення тій самій властивості:
class User {
set name(value) {
this.name = value;
}
}Сетер викличе сам себе без кінця.
Потрібно використовувати іншу внутрішню властивість:
class User {
constructor(name) {
this.name = name;
}
set name(value) {
this._name = value.trim();
}
get name() {
return this._name;
}
}
const user = new User(" Олена ");
console.log(user.name); // ОленаГетер і сетер можуть працювати як пара для однієї властивості:
class Temperature {
constructor(celsius) {
this.celsius = celsius;
}
get fahrenheit() {
return this.celsius * 9 / 5 + 32;
}
set fahrenheit(value) {
this.celsius = (value - 32) * 5 / 9;
}
}
const temperature = new Temperature(0);
console.log(temperature.fahrenheit); // 32
temperature.fahrenheit = 212;
console.log(temperature.celsius); // 100Властивість fahrenheit:
повертає значення за допомогою гетера;
змінює внутрішній стан за допомогою сетера.
this у стрілкових властивостях класуУ сучасному JavaScript можна оголошувати властивості класу зі стрілковими функціями:
class Button {
label = "Натиснути";
handleClick = () => {
console.log(`Кнопка: ${this.label}`);
};
}
const button = new Button();
const callback = button.handleClick;
callback(); // Кнопка: НатиснутиСтрілкова функція створюється для кожного екземпляра окремо та зберігає this екземпляра.
Це відрізняється від звичайного методу прототипу:
class Button {
label = "Натиснути";
handleClick() {
console.log(`Кнопка: ${this.label}`);
}
}У звичайного методу контекст може бути втрачений під час передавання функції. У стрілкової властивості контекст зафіксований.
Однак стрілкові властивості мають особливість: кожен екземпляр отримує власну функцію. Звичайні методи зазвичай економніші щодо пам’яті, оскільки спільно використовуються через прототип.
this у наслідуванніУ дочірньому класі this також посилається на поточний екземпляр.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} видає звук`;
}
}
class Dog extends Animal {
speak() {
return `${this.name} гавкає`;
}
}
const dog = new Dog("Бобік");
console.log(dog.speak()); // Бобік гавкаєЯкщо дочірній клас має конструктор, перед використанням this потрібно викликати super():
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
}
const dog = new Dog("Бобік", "Коргі");
console.log(dog.name); // Бобік
console.log(dog.breed); // Коргіsuper(name) викликає конструктор батьківського класу. До цього виклику використовувати this у конструкторі дочірнього класу не можна.
У наступному прикладі використано:
this у конструкторі та звичайних методах;
гетер і сетер;
статичний метод;
стрілкову callback-функцію;
валідацію даних.
class BankAccount {
constructor(owner, initialBalance = 0) {
this.owner = owner;
this.balance = initialBalance;
}
get balance() {
return this._balance;
}
set balance(value) {
if (typeof value !== "number" || value < 0) {
throw new Error("Баланс має бути невід’ємним числом");
}
this._balance = value;
}
deposit(amount) {
if (amount <= 0) {
throw new Error("Сума поповнення має бути більшою за нуль");
}
this.balance += amount;
}
withdraw(amount) {
if (amount <= 0 || amount > this.balance) {
throw new Error("Некоректна сума зняття");
}
this.balance -= amount;
}
printInfo() {
console.log(`${this.owner}: ${this.balance} грн`);
}
static createEmpty(owner) {
return new this(owner, 0);
}
}
const account = BankAccount.createEmpty("Олена");
account.deposit(1000);
account.withdraw(250);
account.printInfo();
// Олена: 750 грн
const operations = [
() => account.deposit(100),
() => account.withdraw(50),
];
operations.forEach((operation) => {
operation();
});
console.log(account.balance); // 800const printInfo = account.printInfo;
printInfo();Метод втрачає контекст. Використовуйте bind, стрілкову функцію або викликайте метод безпосередньо через об’єкт:
const printInfo = account.printInfo.bind(account);
printInfo();this у статичному методі як властивості екземпляраclass User {
static getName() {
return this.name;
}
}
const user = new User();
// user.getName(); // ПомилкаСтатичний метод належить класу:
User.getName();class Circle {
constructor(radius) {
this.radius = radius;
}
get diameter() {
return this.radius * 2;
}
}
const circle = new Circle(5);
console.log(circle.diameter); // Правильно
// circle.diameter(); // ПомилкаЯкщо сетер названо price, то присвоєння product.price = value викличе саме його. Не потрібно викликати сетер вручну як метод:
product.price = 100;Не використовуйте всередині гетера чи сетера ту саму властивість. Для внутрішнього значення застосовуйте окреме поле, наприклад _value.
this до super()У конструкторі дочірнього класу спочатку потрібно викликати super():
class Child extends Parent {
constructor() {
super();
this.value = 1;
}
}Звичайний метод підходить, коли:
метод викликають через екземпляр;
не потрібно передавати його окремо як callback;
важливо спільно використовувати метод через прототип.
Стрілкова властивість підходить, коли:
метод часто передають як callback;
потрібно автоматично зберігати контекст екземпляра;
метод використовується, наприклад, як обробник події.
Якщо контекст потрібно прив’язати лише в одному місці, часто достатньо bind або обгортки-стрілки.
У звичайному методі класу this посилається на екземпляр, через який викликали метод.
У конструкторі this — це новий екземпляр класу.
this залежить від способу виклику функції.
Під час передавання методу як callback контекст може бути втрачений.
bind фіксує this для нової функції.
Стрілкові функції використовують this із зовнішнього контексту.
Статичні методи належать класу, а не його екземплярам.
У статичному методі this зазвичай посилається на сам клас.
Гетер читає значення як властивість.
Сетер обробляє присвоєння значення властивості.
Для гетерів і сетерів потрібно використовувати окреме внутрішнє поле, щоб уникнути нескінченної рекурсії.
У конструкторі дочірнього класу перед використанням this потрібно викликати super().