Suggest an editImprove this articleRefine the answer for “Private class fields outside a class”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**No. A private field `#field` is not reachable outside the class body in any way: a direct `obj.#field` access is a syntax error that the engine throws before the code even runs.** Workarounds fail too: `obj['#field']` gives `undefined`, `Object.keys`, `Object.getOwnPropertyNames` and `Reflect.ownKeys` do not see private fields, and `JSON.stringify` does not serialise them. The only way to interact with such a field is the class public interface: methods, getters and setters declared in that same class. ```javascript class User { #password = 'secret-1'; } const u = new User(); console.log(u['#password']); // undefined, this is not the same field console.log(Reflect.ownKeys(u)); // [] // console.log(u.#password); // SyntaxError at parse time ``` **Key point:** privacy in JS is enforced by syntax, not by convention, so from outside a `#field` cannot be read, written, enumerated or serialised.Shown above the full answer for quick recall.Answer (EN)Image**You cannot reach a private class property from outside: `obj.#field` outside the class body is invalid syntax, and the engine throws a `SyntaxError` before execution even starts.** This is not a convention like the `_` prefix, it is real language level privacy (ES2022) that cannot be bypassed through a string key, through `Reflect`, or through serialisation. ## Theory ### TL;DR - `obj.#field` outside the class is a `SyntaxError` at parse time, not a runtime error. - `obj['#field']` is not an access to the private field: it returns `undefined` or creates an ordinary public property named `'#field'`. - Private fields never appear in `Object.keys`, `Object.entries`, `Object.getOwnPropertyNames`, `Reflect.ownKeys` or `for...in`. - `JSON.stringify` ignores private fields: an object that only has them serialises to `{}`. - The only legal access is through public methods, getters and setters declared in that same class. ### Quick example ```javascript class User { #password = 'secret-1'; checkPassword(pwd) { return pwd === this.#password; } } const u = new User(); console.log(u.checkPassword('secret-1')); // true, access through a class method // console.log(u.#password); // SyntaxError: Private field '#password' // // must be declared in an enclosing class ``` The `u.#password` line will not even run: its mere presence in the file makes the whole module fail to parse. ### Why it is a parse error, not a runtime error Privacy in JavaScript is implemented at the syntax level, not through a naming convention. When the engine sees the `#field` token, it performs two checks at compile time: 1. Is this private name declared in the lexical environment of the class that encloses the expression? 2. If not, it throws a `SyntaxError` and does not even attempt to execute the code. That is why such an error cannot be caught with a `try/catch` around the access: by the time `try` could run, the script has already failed to compile. ```javascript class Example { #value = 42; } const e = new Example(); try { // console.log(e.#value); // uncommenting breaks the WHOLE file, not just this block } catch (err) { console.log('we never get here'); } ``` ### Workarounds do not work Private fields are not ordinary object properties, so no enumeration technique reveals them. ```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); // never iterates } ``` Serialisation does not help either: private fields do not reach JSON. ```javascript class Account { #balance = 1000; } const acc = new Account(); console.log(JSON.stringify(acc)); // '{}' ``` > This is a handy property: private fields do not leak into logs or an HTTP response body by accident, because they are simply absent from the `JSON.stringify` result. ### A private field cannot be shadowed Writing to a private field from outside is a syntax error as well. Writing through a string key creates a completely different, public property that has nothing to do with the private one. ```javascript class Example { #value = 42; getValue() { return this.#value; } } const e = new Example(); // e.#value = 100; // SyntaxError e['#value'] = 100; // creates a new ordinary property console.log(e); // Example { '#value': 100 } console.log(e.getValue()); // 42, the real private field did not change ``` In other words, from outside you can only pollute the object with a similar looking key, while the real class state stays untouched. ### The only correct access: the public interface If a private value has to be visible outside, the class itself decides in what shape to expose it. That is what methods, getters and setters are for: they can touch `#field` because they are declared in the same class. ```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('Password is too short'); } this.#password = newPwd; } } const u = new User(); console.log(u.maskedPassword); // '********' console.log(u.checkPassword('secret-1')); // true u.setPassword('secret-2'); ``` The field stays protected, but the class offers controlled, safe access: masking on read and validation on write. ### Checking whether a private field exists The only `#field` operation allowed outside of a `this` access is the `#field in obj` check, and even that works only inside the class body. It is used as a brand check, to tell whether an arbitrary object is really an instance of this class. ```javascript class User { #password = 'secret-1'; static isUser(obj) { return #password in obj; // true only for User instances } } console.log(User.isUser(new User())); // true console.log(User.isUser({})); // false ``` Outside the class that expression is a `SyntaxError` again, so it is not a way to read the value from outside, it is a way to check a type safely from inside. ### Summary table | Action from outside the class | Result | | --- | --- | | `obj.#field` | `SyntaxError` at parse time | | `obj.#field = 1` | `SyntaxError` at parse time | | `obj['#field']` | `undefined`, or creates a separate public property | | `Object.keys(obj)` | private field not visible | | `Object.getOwnPropertyNames(obj)` | private field not visible | | `Reflect.ownKeys(obj)` | private field not visible | | `for...in` | private field not visible | | `JSON.stringify(obj)` | private field not visible | | a class method, getter or setter | works, this is the only way | ### Common mistakes - **Confusing `#field` with the `_field` convention.** A `_password` property is an ordinary public field: it shows up in `Object.keys` and anyone can overwrite it. `#password` is physically unreachable. - **Assuming `obj['#field']` is the same field.** It is a separate string key; writing through it creates a new public slot and does not change the private state. - **Trying to catch the error with `try/catch`.** A `SyntaxError` happens before execution, so the whole file or module fails, not one block. - **Expecting private fields in JSON.** They disappear after `JSON.stringify`, so a DTO has to be built explicitly, for example with a `toJSON()` method. - **Declaring a private field only in the constructor.** The private name must be declared in the class body (`#password = ...` or `#password;`), otherwise assigning `this.#password` is a syntax error too. - **Expecting private fields to be inherited.** A subclass does not see the parent `#field`: private names belong to one specific class body, not to the prototype chain.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.