Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Геттери і сеттери в класі». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Геттер (`get`) і сеттер (`set`), це методи класу, які викликаються автоматично при читанні та записі властивості, тобто «розумні властивості» із синтаксисом звичайного поля.** Під капотом це accessor-дескриптор (`Object.defineProperty` з функціями `get` і `set`), тому саме значення вони не зберігають, а керують ним: валідують вхідні дані, обчислюють результат на льоту, дають контрольований доступ до приватних полів `#field`. Якщо є лише геттер, властивість фактично тільки для читання. ```javascript class Account { #balance = 0; get balance() { return this.#balance; // виклик при читанні acc.balance } set balance(value) { if (value < 0) throw new Error('Balance cannot be negative'); this.#balance = value; // виклик при записі acc.balance = ... } } ``` **Ключове:** геттер і сеттер виглядають як поле, але виконують код, тому дають логіку, валідацію та обчислювані значення без зміни API класу.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Геттер і сеттер, це пара спеціальних методів класу, які виконуються автоматично при читанні (`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.prop` | `obj.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.** Обчислювані властивості не серіалізуються, бо неперелічувані і лежать на прототипі. - **Важкі обчислення в геттері.** Читання властивості виглядає дешевим, тому виклик у циклі чи в рендері легко стає вузьким місцем; кешуйте результат. - **Побічні ефекти в геттері.** Мережеві запити, мутації стану і логи роблять код непередбачуваним: геттер має лише повертати значення.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.