MENU
VS Private Class Fields
Before ECMAScript 2022 introduced native #private class fields (6.6), a Symbol (10) stored in a closure-scoped variable was the closest thing to a private class member (6.6). The two techniques look similar but differ in important ways.
| Symbol-keyed property | #private field | |
| truly inaccessible from outside? | no – discoverable via Object.getOwnPropertySymbols() | yes – not reachable by any reflective API |
| enumerable / JSON.stringify()? | excluded, but still a real, visitable property | excluded entirely; not a property at all |
| shared across a class hierarchy? | yes, if the Symbol reference is shared/exported | no – each class's #name is lexically private to it |
| needs a declaration in the class body? | no – can be added to any object dynamically | yes – must be declared as a field/method in the class |
| safe existence check | symbolKey in obj | #field in obj (ergonomic brand check, 6.6) |
The Symbol can still be leaked and read if a reference to it escapes; the #private field genuinely cannot, from outside the class.
const ageSym = Symbol('age');
class PersonBySymbol {
constructor(age) { this[ageSym] = age; }
}
class PersonByField {
#age;
constructor(age) { this.#age = age; }
}
var p1 = new PersonBySymbol(30);
var p2 = new PersonByField(30);
console.log(Object.getOwnPropertySymbols(p1).length); // the Symbol is discoverable
console.log(p1[Object.getOwnPropertySymbols(p1)[0]]); // and thus readable
// console.log(Object.getOwnPropertyNames(p2)); // [], #age isn't even listed
// console.log(p2['#age']); // undefined, '#age' is not a normal string key1
30
In new code, prefer #private fields/methods for genuine encapsulation within a class; reach for a module-scoped Symbol only when a key needs to be shared across otherwise-unrelated objects, or attached to a plain object rather than a class instance.