Private class fields outside a class
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.#fieldoutside the class is aSyntaxErrorat parse time, not a runtime error.obj['#field']is not an access to the private field: it returnsundefinedor creates an ordinary public property named'#field'.- Private fields never appear in
Object.keys,Object.entries,Object.getOwnPropertyNames,Reflect.ownKeysorfor...in. JSON.stringifyignores 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
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 classThe 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:
- Is this private name declared in the lexical environment of the class that encloses the expression?
- If not, it throws a
SyntaxErrorand 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.
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.
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.
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.stringifyresult.
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.
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 changeIn 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.
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.
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({})); // falseOutside 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
#fieldwith the_fieldconvention. A_passwordproperty is an ordinary public field: it shows up inObject.keysand anyone can overwrite it.#passwordis 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. ASyntaxErrorhappens 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 atoJSON()method. - Declaring a private field only in the constructor. The private name must be declared in the class body (
#password = ...or#password;), otherwise assigningthis.#passwordis 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.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.