Skip to main content

Приватні поля поза класом

Звернутися до приватної властивості класу ззовні неможливо: obj.#field поза тілом класу це недопустимий синтаксис, і рушій кидає SyntaxError ще до старту виконання. Це не домовленість на кшталт префікса _, а справжня мовна приватність (ES2022), яку не обійти ні через рядковий ключ, ні через Reflect, ні через серіалізацію.

Теорія

TL;DR

  • obj.#field поза класом, це SyntaxError на етапі парсингу, а не помилка під час виконання.
  • obj['#field'] не є зверненням до приватного поля: воно поверне undefined або створить звичайну публічну властивість з ім'ям '#field'.
  • Приватні поля не потрапляють у Object.keys, Object.entries, Object.getOwnPropertyNames, Reflect.ownKeys і for...in.
  • JSON.stringify приватні поля ігнорує: об'єкт лише з приватними полями серіалізується як {}.
  • Єдиний легальний доступ, це публічні методи, getter та setter, оголошені в тому самому класі.

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

javascript
class User { #password = 'secret-1'; checkPassword(pwd) { return pwd === this.#password; } } const u = new User(); console.log(u.checkPassword('secret-1')); // true, доступ через метод класу // console.log(u.#password); // SyntaxError: Private field '#password' // // must be declared in an enclosing class

Рядок з u.#password навіть не виконається: достатньо, щоб він просто був у файлі, і весь модуль не пройде парсинг.

Чому це помилка парсингу, а не рантайму

Приватність у JavaScript реалізована на рівні синтаксису, а не через домовленість про іменування. Коли рушій бачить токен #field, він робить дві перевірки ще на етапі компіляції:

  1. Чи оголошене це приватне ім'я в лексичному оточенні класу, всередині якого стоїть вираз.
  2. Якщо ні, він кидає SyntaxError і навіть не намагається виконати код.

Саме тому таку помилку не можна перехопити через try/catch навколо звернення: до моменту, коли try міг би спрацювати, скрипт уже не скомпілювався.

javascript
class Example { #value = 42; } const e = new Example(); try { // console.log(e.#value); // розкоментування ламає ВЕСЬ файл, а не цей блок } catch (err) { console.log('сюди ми не потрапимо'); }

Обхідні шляхи не працюють

Приватні поля не є звичайними властивостями об'єкта, тому жоден спосіб перелічування їх не покаже.

javascript
class User { #password = 'secret-1'; } const u = new User(); console.log(u['#password']); // undefined console.log(Object.keys(u)); // [] console.log(Object.entries(u)); // [] console.log(Object.getOwnPropertyNames(u)); // [] console.log(Reflect.ownKeys(u)); // [] for (const key in u) { console.log(key); // жодної ітерації }

Не допоможе й серіалізація: приватні поля не потрапляють у JSON.

javascript
class Account { #balance = 1000; } const acc = new Account(); console.log(JSON.stringify(acc)); // '{}'

Це зручна властивість: приватні поля не «протікають» у логи та в тіло HTTP-відповіді випадково, бо їх немає в результаті JSON.stringify.

Приватне поле не можна підмінити

Запис у приватне поле ззовні так само є синтаксичною помилкою. А запис через рядковий ключ створює зовсім іншу, публічну властивість, яка з приватним полем ніяк не пов'язана.

javascript
class Example { #value = 42; getValue() { return this.#value; } } const e = new Example(); // e.#value = 100; // SyntaxError e['#value'] = 100; // створює нову звичайну властивість console.log(e); // Example { '#value': 100 } console.log(e.getValue()); // 42, справжнє приватне поле не змінилося

Тобто зовні можна лише «забруднити» об'єкт схожим на вигляд ключем, але реальний стан класу лишається недоторканим.

Єдиний правильний доступ: публічний інтерфейс

Якщо приватне значення потрібне назовні, клас сам вирішує, у якому вигляді його віддати. Для цього є методи, getter та setter, які мають доступ до #field, бо оголошені в тому самому класі.

javascript
class User { #password = 'secret-1'; get maskedPassword() { return '*'.repeat(this.#password.length); } checkPassword(pwd) { return pwd === this.#password; } setPassword(newPwd) { if (newPwd.length < 5) { throw new Error('Пароль занадто короткий'); } this.#password = newPwd; } } const u = new User(); console.log(u.maskedPassword); // '********' console.log(u.checkPassword('secret-1')); // true u.setPassword('secret-2');

Поле лишається захищеним, але клас надає контрольований, безпечний доступ: маскування при читанні та валідацію при записі.

Перевірка наявності приватного поля

Єдина операція над #field, дозволена не через this, це перевірка #field in obj, але й вона працює тільки всередині тіла класу. Її використовують як «brand check», щоб зрозуміти, чи є довільний об'єкт екземпляром саме цього класу.

javascript
class User { #password = 'secret-1'; static isUser(obj) { return #password in obj; // true лише для екземплярів User } } console.log(User.isUser(new User())); // true console.log(User.isUser({})); // false

Поза класом такий вираз знову буде SyntaxError, тож це не спосіб дістати значення ззовні, а спосіб безпечно перевірити тип усередині класу.

Підсумкова таблиця

Дія ззовні класуРезультат
obj.#fieldSyntaxError на етапі парсингу
obj.#field = 1SyntaxError на етапі парсингу
obj['#field']undefined, або створює окрему публічну властивість
Object.keys(obj)приватного поля не видно
Object.getOwnPropertyNames(obj)приватного поля не видно
Reflect.ownKeys(obj)приватного поля не видно
for...inприватного поля не видно
JSON.stringify(obj)приватного поля не видно
метод, getter або setter класупрацює, це єдиний шлях

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

  • Плутати #field з угодою _field. Властивість _password, це звичайне публічне поле, його видно в Object.keys і його можна перезаписати. #password недоступне фізично.
  • Думати, що obj['#field'] це те саме поле. Це окремий рядковий ключ; запис через нього створює новий публічний слот і не змінює приватного стану.
  • Пробувати перехопити помилку через try/catch. SyntaxError виникає до виконання, тому впаде весь файл або модуль, а не окремий блок.
  • Розраховувати, що приватні поля потраплять у JSON. Після JSON.stringify вони зникають, тому DTO треба формувати явно, наприклад методом toJSON().
  • Оголошувати приватне поле лише в конструкторі. Приватне ім'я має бути оголошене в тілі класу (#password = ... або #password;), інакше присвоєння this.#password теж стане синтаксичною помилкою.
  • Очікувати успадкування приватних полів. Підклас не бачить #field батьківського класу: приватні імена належать конкретному тілу класу, а не ланцюжку прототипів.

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

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

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