All topics
Objectsintermediate

Private Class Fields and Encapsulation

The # syntax for true private fields and methods in JavaScript classes, enforced by the engine rather than by convention.

# makes a field genuinely private. The engine enforces it, unlike the underscore convention it replaces.

Public class fields are like an office with a glass door anyone can walk through and rearrange the furniture in; private fields are like a locked back office that only employees hired specifically for that office (methods of that exact class) have a key to, and even attempting to peek in from outside gets stopped at the door before you even get close.

Key Concepts

1
    class Account {
      #balance = 0;
      static #count = 0;
2
      deposit(n) {
        if (n <= 0) throw new RangeError('must be positive');
        this.#balance += n;
      }
3
      get balance() { return this.#balance; }
4
      #audit(action) { }        // a private method
    }
5
    const a = new Account();
    a.#balance;          // SyntaxError -- not a runtime error
    a['#balance'];       // undefined -- not a string key
6
It is a syntax error, not a runtime one, which means the code cannot even be written. Compare with the old convention:
7
    this._balance = 0;   // private by agreement; reachable by anyone
8
And the other old approach, closures.
    function createAccount() {
      let balance = 0;              // genuinely private
      return { deposit: n => balance += n };
    }
9
That also works, and it creates a new copy of every method per instance. Private fields keep the methods on the prototype and still give real privacy.
10
They are not in Object.keys, JSON.stringify or the debugger's normal property list, so they do not leak through serialisation.
Object.keysJSON.stringify
11
Checking for one — the in form.
    class C {
      #x;
      static isC(o) { return #x in o; }     // safe brand check
    }
in
12
That is how a class tests whether an arbitrary object is really one of its instances, without throwing.
13
What to keep private. Anything whose representation you may change: the internal data structure, caches, intermediate state. Expose behaviour and computed values.
14
    #items = [];                    // an array today, a Map tomorrow
    get count() { return this.#items.length; }
15
Once items is public, changing it from an array breaks every caller.
items
16
The caution. Private fields are inaccessible to subclasses — that is protected, which JavaScript does not have. A subclass needing the value needs a protected-by-convention accessor or a rethink of the hierarchy.
protected
17
And test code cannot reach them either, which is correct: tests should exercise the public behaviour, and a test that needs internal state is usually testing the wrong thing.
18
What the interviewer is probing.1. "How does # differ from an underscore prefix?" Probing: enforcement. Stalls: "It is the same convention." Moves up: # is enforced by the language — access from outside is a SyntaxError, not a runtime check, and the field is invisible to JSON and reflection.
19
2. "Can you check whether an object has a private field?" Probing: the brand check. Stalls: "No." Moves up: #field in obj, which is how you reliably identify instances of your class.
20
3. "What is a WeakMap used for in older code?" Probing: the predecessor. Stalls: "Caching." Moves up: storing per-instance private data keyed by the instance, which was the pre-# way to get real privacy.
21
4. "Are private methods supported?" Probing: the scope. Stalls: "Only fields." Moves up: yes, including static ones, with the same # syntax.