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 key

1 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.