Пошук уроків, статей та іншого контенту
Створюватимете гетери й сетери для контролю читання та запису властивостей об’єкта.
Гетер (getter) — це метод, який викликається під час читання властивості об’єкта.
Сетер (setter) — це метод, який викликається під час запису значення у властивість об’єкта.
Вони дають змогу контролювати доступ до даних:
перевіряти значення перед збереженням;
обчислювати значення динамічно;
приховувати внутрішню структуру об’єкта;
виконувати додаткові дії під час читання або запису.
Гетери та сетери використовуються так само, як звичайні властивості:
object.property;
object.property = value;Дужки для виклику не використовуються, хоча всередині це методи.
Гетер оголошується за допомогою ключового слова get, а сетер — set.
const user = {
firstName: "Олена",
lastName: "Коваль",
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(value) {
const [firstName, lastName] = value.split(" ");
this.firstName = firstName;
this.lastName = lastName;
}
};
console.log(user.fullName);
// Олена Коваль
user.fullName = "Марія Бондар";
console.log(user.firstName);
// Марія
console.log(user.lastName);
// Бондар
console.log(user.fullName);
// Марія БондарУ цьому прикладі:
fullName — не збережена властивість;
під час читання user.fullName виконується гетер;
під час запису user.fullName = "Марія Бондар" виконується сетер;
усередині сетера змінюються firstName і lastName.
Гетери зручно використовувати для властивостей, які можна обчислити з інших даних.
const rectangle = {
width: 10,
height: 5,
get area() {
return this.width * this.height;
}
};
console.log(rectangle.area);
// 50
rectangle.width = 20;
console.log(rectangle.area);
// 100area не зберігається окремо. Її значення обчислюється щоразу під час читання.
Це допомагає уникнути дублювання даних. Якщо зберігати площу окремо, після зміни width або height її потрібно було б оновлювати вручну.
Сетер може не дозволити записати некоректне значення.
const product = {
_price: 0,
get price() {
return this._price;
},
set price(value) {
if (typeof value !== "number" || value < 0) {
throw new Error("Ціна повинна бути невід’ємним числом");
}
this._price = value;
}
};
product.price = 150;
console.log(product.price);
// 150
product.price = -20;
// Error: Ціна повинна бути невід’ємним числомПрефікс _ у _price — це лише домовленість, яка показує, що властивість призначена для внутрішнього використання. Сам по собі такий префікс не робить властивість приватною.
У класах гетери та сетери оголошуються аналогічно:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
get age() {
return this._age;
}
set age(value) {
if (!Number.isInteger(value) || value < 0) {
throw new Error("Вік повинен бути невід’ємним цілим числом");
}
this._age = value;
}
get description() {
return `${this.name}, ${this.age} років`;
}
}
const user = new User("Андрій", 28);
console.log(user.age);
// 28
console.log(user.description);
// Андрій, 28 років
user.age = 29;
console.log(user.description);
// Андрій, 29 років
user.age = -1;
// Error: Вік повинен бути невід’ємним цілим числомКоли конструктор виконує:
this.age = age;він не записує значення безпосередньо в age. Замість цього викликається сетер age, тому перевірка працює вже під час створення об’єкта.
У сучасному JavaScript для справді приватних полів можна використовувати префікс #.
class BankAccount {
#balance = 0;
constructor(initialBalance = 0) {
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;
}
}
const account = new BankAccount(100);
console.log(account.balance);
// 100
account.deposit(50);
console.log(account.balance);
// 150
account.balance = 200;
console.log(account.balance);
// 200
// console.log(account.#balance);
// Синтаксична помилка: приватне поле недоступне зовніПоле #balance не можна прочитати або змінити безпосередньо за межами класу. Доступ до нього відбувається через гетер, сетер і метод deposit.
Щоб створити властивість лише для читання, можна оголосити тільки гетер без сетера.
class Circle {
constructor(radius) {
this.radius = radius;
}
get diameter() {
return this.radius * 2;
}
get area() {
return Math.PI * this.radius ** 2;
}
}
const circle = new Circle(5);
console.log(circle.diameter);
// 10
console.log(circle.area);
// 78.53981633974483
// circle.area = 100;
// У строгому режимі виникне TypeErrordiameter і area можна читати, але для них не передбачено контрольованого запису.
Варто пам’ятати: відсутність сетера не завжди захищає від запису однаково в усіх режимах. У строгому режимі спроба записати значення у властивість без сетера спричинить помилку.
Значення, яке присвоюється властивості, не можна замінити значенням, повернутим із сетера.
const settings = {
_theme: "light",
set theme(value) {
this._theme = value.toLowerCase();
return this._theme;
},
get theme() {
return this._theme;
}
};
const result = (settings.theme = "DARK");
console.log(settings.theme);
// dark
console.log(result);
// DARKРезультатом виразу присвоєння залишається значення праворуч від =, а не результат роботи сетера. Тому сетер використовують для перевірки або перетворення даних, а не для повернення нового значення в операцію присвоєння.
Гетер і сетер зазвичай мають однакове ім’я властивості:
class Temperature {
constructor(celsius) {
this.celsius = celsius;
}
get celsius() {
return this._celsius;
}
set celsius(value) {
if (typeof value !== "number") {
throw new TypeError("Температура повинна бути числом");
}
this._celsius = value;
}
get fahrenheit() {
return this.celsius * 9 / 5 + 32;
}
}
const temperature = new Temperature(20);
console.log(temperature.celsius);
// 20
console.log(temperature.fahrenheit);
// 68
temperature.celsius = 30;
console.log(temperature.fahrenheit);
// 86celsius має і гетер, і сетер, а fahrenheit — лише гетер, оскільки це обчислюване представлення температури.
thisУ гетері та сетері this посилається на об’єкт, для якого читається або змінюється властивість.
const counter = {
_value: 0,
get value() {
return this._value;
},
set value(newValue) {
this._value = newValue;
}
};
counter.value = 10;
console.log(counter.value);
// 10Якщо передати гетер або сетер окремо від об’єкта, контекст може бути втрачений. Для звичайного доступу через object.property це не є проблемою.
Гетери й сетери є частиною дескриптора властивості. Їх можна створити за допомогою Object.defineProperty().
const user = {
firstName: "Ірина",
lastName: "Мельник"
};
Object.defineProperty(user, "fullName", {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(value) {
const parts = value.split(" ");
this.firstName = parts[0];
this.lastName = parts[1];
},
enumerable: true,
configurable: true
});
console.log(user.fullName);
// Ірина Мельник
user.fullName = "Наталія Шевченко";
console.log(user.fullName);
// Наталія ШевченкоДля повсякденного коду зазвичай достатньо синтаксису об’єктного літерала або класу. Object.defineProperty() корисний, коли властивість потрібно створювати динамічно або точно налаштувати її дескриптор.
Порівняймо гетер і звичайний метод:
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
get area() {
return this.width * this.height;
}
calculateArea() {
return this.width * this.height;
}
}
const rectangle = new Rectangle(10, 5);
console.log(rectangle.area);
// 50
console.log(rectangle.calculateArea());
// 50Гетер доречний, коли значення сприймається як властивість об’єкта:
rectangle.area;Метод краще використовувати, якщо операція:
виконує складну дію;
має побічні ефекти;
приймає аргументи;
не є просто характеристикою об’єкта.
Гетер не повинен несподівано змінювати стан об’єкта. Читання властивості зазвичай очікується як безпечна операція.
Не можна всередині гетера звертатися до тієї самої властивості:
const user = {
get name() {
return this.name;
}
};this.name знову викликає гетер name, який знову викликає сам себе. Це призведе до нескінченної рекурсії.
Для збереження значення використовуйте інше поле:
const user = {
_name: "Олена",
get name() {
return this._name;
}
};Або приватне поле класу:
class User {
#name;
constructor(name) {
this.#name = name;
}
get name() {
return this.#name;
}
}Гетер читається без дужок:
console.log(user.name);Так буде помилково:
console.log(user.name());
// TypeError, якщо name є гетеромСетер повинен приймати рівно одне значення:
const object = {
set value(newValue) {
console.log(newValue);
}
};Сетер без параметра не зможе отримати значення, яке присвоюється властивості.
Такий код спричинить рекурсію:
class User {
set name(value) {
this.name = value;
}
}Потрібно використовувати інше внутрішнє поле:
class User {
set name(value) {
this._name = value;
}
get name() {
return this._name;
}
}Гетер може обчислювати значення, але не варто поміщати в нього важкі операції, мережеві запити або непередбачувані побічні ефекти. Читання властивості має залишатися простим і зрозумілим.
Нижче клас кошика використовує:
приватне поле для товарів;
сетер для перевірки знижки;
гетер для обчислення кількості товарів;
гетер для обчислення загальної вартості.
class ShoppingCart {
#items = [];
#discount = 0;
addItem(name, price, quantity = 1) {
if (typeof name !== "string" || name.trim() === "") {
throw new Error("Назва товару не може бути порожньою");
}
if (typeof price !== "number" || price < 0) {
throw new Error("Ціна повинна бути невід’ємним числом");
}
if (!Number.isInteger(quantity) || quantity <= 0) {
throw new Error("Кількість повинна бути додатним цілим числом");
}
this.#items.push({ name, price, quantity });
}
get itemsCount() {
return this.#items.reduce((total, item) => {
return total + item.quantity;
}, 0);
}
get subtotal() {
return this.#items.reduce((total, item) => {
return total + item.price * item.quantity;
}, 0);
}
get discount() {
return this.#discount;
}
set discount(value) {
if (typeof value !== "number" || value < 0 || value > 100) {
throw new Error("Знижка повинна бути числом від 0 до 100");
}
this.#discount = value;
}
get total() {
return this.subtotal * (1 - this.discount / 100);
}
}
const cart = new ShoppingCart();
cart.addItem("Клавіатура", 2000, 1);
cart.addItem("Миша", 1000, 2);
cart.discount = 10;
console.log(cart.itemsCount);
// 3
console.log(cart.subtotal);
// 4000
console.log(cart.total);
// 3600Гетер описується через get і викликається під час читання властивості.
Сетер описується через set і викликається під час присвоєння значення.
Гетери не викликаються з дужками.
Сетери приймають одне значення.
Гетери зручні для обчислюваних властивостей.
Сетери дають змогу перевіряти та нормалізувати вхідні дані.
Для внутрішнього збереження значень використовуйте окреме поле, наприклад _value, або приватне поле #value.
Гетер без сетера створює властивість, яку можна лише читати.
Гетер не повинен мати несподіваних побічних ефектів.