Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Приватні поля поза класом». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Ні. Приватне поле `#field` недоступне поза тілом класу взагалі ніяк: пряме звернення `obj.#field` є синтаксичною помилкою, яку рушій кидає ще до виконання коду.** Обхідні шляхи теж не працюють: `obj['#field']` дає `undefined`, `Object.keys`, `Object.getOwnPropertyNames` і `Reflect.ownKeys` приватних полів не бачать, а `JSON.stringify` їх не серіалізує. Єдиний спосіб взаємодіяти з таким полем, це публічний інтерфейс класу: методи, getter та setter, оголошені в тому самому класі. ```javascript class User { #password = 'secret-1'; } const u = new User(); console.log(u['#password']); // undefined, це не те саме поле console.log(Reflect.ownKeys(u)); // [] // console.log(u.#password); // SyntaxError ще на етапі парсингу ``` **Ключове:** приватність у JS реалізована на рівні синтаксису, а не домовленості, тому ззовні поле `#field` не читається, не пишеться, не перелічується і не серіалізується.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Звернутися до приватної властивості класу ззовні неможливо: `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.#field` | `SyntaxError` на етапі парсингу | | `obj.#field = 1` | `SyntaxError` на етапі парсингу | | `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` батьківського класу: приватні імена належать конкретному тілу класу, а не ланцюжку прототипів.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.