Skip to main content

Геттери і сеттери в класі

Геттер і сеттер, це пара спеціальних методів класу, які виконуються автоматично при читанні (obj.prop) та присвоєнні (obj.prop = value). Ззовні властивість виглядає як звичайне поле, але насправді за нею стоїть код, який може валідувати, обчислювати чи логувати.

Теорія

TL;DR

  • get name() спрацьовує на читанні obj.name, set name(value), на присвоєнні obj.name = value.
  • Це accessor-властивість: значення в ній не зберігається, воно лише обчислюється або делегується в інше поле.
  • Лише геттер, властивість тільки для читання; лише сеттер, властивість тільки для запису (читання дасть undefined).
  • Ідеально поєднуються з приватними полями #field: дані захищені, доступ контрольований.
  • Той самий механізм доступний поза класом через Object.defineProperty або літерал об'єкта.

Швидкий приклад

javascript
class User { constructor(name) { this._name = name; // «приватне» поле за домовленістю } get name() { console.log('getter called'); return this._name; } set name(value) { console.log('setter called'); if (value.length < 2) throw new Error('Name is too short'); this._name = value; } } const u = new User('Maria'); console.log(u.name); // 'getter called', потім 'Maria' u.name = 'Oleh'; // 'setter called' console.log(u.name); // 'Oleh'

Синтаксис виклику лишається як у звичайного поля: жодних дужок, жодного getName().

Як це працює під капотом

get і set у тілі класу, це скорочення для accessor-дескриптора властивості. Формально клас робить приблизно те саме, що й Object.defineProperty:

javascript
const user = { _name: 'Maria' }; Object.defineProperty(user, 'name', { get() { return this._name.toUpperCase(); }, set(value) { this._name = value.trim(); } }); user.name = ' oleh '; console.log(user.name); // 'OLEH'

Важливі наслідки:

  • Аксесор кладеться на прототип класу, а не на екземпляр, тому не дублюється в кожному об'єкті.
  • Він неперелічуваний (enumerable: false), тож не потрапляє в Object.keys(instance) і в результат JSON.stringify(instance). Якщо обчислюване значення потрібне в JSON, додайте метод toJSON().
  • Дескриптор можна прочитати: Object.getOwnPropertyDescriptor(User.prototype, 'name') поверне об'єкт з полями get і set.
  • Аксесор може бути статичним: static get version() { return '1.0'; } читається як User.version.

Геттер без сеттера і сеттер без геттера

Якщо визначено лише геттер, властивість стає тільки для читання. Її типово використовують для обчислюваних значень: area, fullName, priceWithTax.

javascript
class Circle { constructor(radius) { this.radius = radius; } get area() { return Math.PI * this.radius ** 2; } } const c = new Circle(10); console.log(c.area); // 314.159... c.area = 100; // у модулі або в strict mode: TypeError console.log(c.area); // 314.159..., значення не змінилося

У нестрогому режимі таке присвоєння просто мовчки ігнорується, а в модулях і в strict mode кидає TypeError: Cannot set property area. Код класів завжди строгий, але сам рядок присвоєння виконується у своєму контексті, тому поведінка залежить від нього.

Дзеркальний випадок, сеттер без геттера, зручний, коли треба лише реагувати на запис і перерахувати внутрішні дані.

javascript
class Temperature { set celsius(value) { this.fahrenheit = value * 1.8 + 32; } } const t = new Temperature(); t.celsius = 25; console.log(t.fahrenheit); // 77 console.log(t.celsius); // undefined, геттера немає

Робота з приватними полями

Найчастіше геттер і сеттер, це фасад над приватним полем #field. Дані недоступні ззовні, але клас сам вирішує, як їх віддавати і що приймати.

javascript
class Account { #balance = 0; get balance() { return this.#balance; } set balance(value) { if (value < 0) throw new Error('Balance cannot be negative'); this.#balance = value; } } const acc = new Account(); acc.balance = 100; console.log(acc.balance); // 100 // acc.balance = -50; // Error: Balance cannot be negative

Такий підхід дає головну перевагу аксесорів: публічний контракт (acc.balance) лишається стабільним, а внутрішнє зберігання можна змінити будь-коли.

Обчислювані властивості

Геттер разом із сеттером дозволяє розкласти складене значення на частини і зібрати назад.

javascript
class User { constructor(first, last) { this.first = first; this.last = last; } get fullName() { return `${this.first} ${this.last}`; } set fullName(value) { const [first, last] = value.split(' '); this.first = first; this.last = last; } } const u = new User('Maria', 'Koval'); console.log(u.fullName); // 'Maria Koval' u.fullName = 'Oleh Shevchenko'; console.log(u.first); // 'Oleh' console.log(u.last); // 'Shevchenko'

Властивість fullName не існує як поле в пам'яті: вона щоразу обчислюється з first і last, тому неможливо отримати розсинхронізовані дані.

Порівняння зі звичайною властивістю

ОсобливістьЗвичайна властивістьГеттер або сеттер
Зберігає значеннятакні, обчислює або делегує
Виконує код при доступінітак, автоматично
Можна додати валідацію чи логікунітак
Працює з приватними полямитактак
Синтаксис зверненняobj.propobj.prop, такий самий
Живе на екземплярітакна прототипі класу
Потрапляє в JSON.stringifyтакні

Типові помилки

  • Викликати як метод. u.name() кине TypeError: u.name is not a function, бо геттер повертає значення, а не функцію.
  • Нескінченна рекурсія. Якщо в set name(value) написати this.name = value, сеттер викличе сам себе і призведе до переповнення стека. Потрібне окреме сховище: this._name або #name.
  • Однакове ім'я для аксесора і поля. get value() разом з полем value = 1 не працює: поле екземпляра затінює аксесор із прототипу.
  • Очікувати аксесори в JSON. Обчислювані властивості не серіалізуються, бо неперелічувані і лежать на прототипі.
  • Важкі обчислення в геттері. Читання властивості виглядає дешевим, тому виклик у циклі чи в рендері легко стає вузьким місцем; кешуйте результат.
  • Побічні ефекти в геттері. Мережеві запити, мутації стану і логи роблять код непередбачуваним: геттер має лише повертати значення.

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

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

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