JavaScript Private Class Fields
By Flavio Copes
Learn how JavaScript private class fields, declared with a # prefix, finally enforce real privacy on a class instead of the old underscore convention.
Private class fields let you declare class properties that can’t be accessed from outside the class. You declare them by prefixing the name with #, and the language enforces the privacy for you.
Before their introduction, we could not really enforce private properties on a class. We used conventions instead, maybe using _ as an hint that the field is private, like this:
class Counter {
_count = 0
increment() {
this._count++
}
}
But we could access the count using
const counter = new Counter()
counter._count
Nothing stopped us. The underscore was a polite request, not a rule. Anyone could read the value, overwrite it, or build code that depended on it. Then the day you rename or remove that “private” field, you break someone else’s code.
How to declare a private field
We can now use private class fields that enforce private fields:
class Counter {
#count = 0
increment() {
this.#count++
}
}
We now can’t access this value from the outside. Trying to access it will raise a syntax error:
const counter = new Counter()
counter.increment()
counter.#count
//SyntaxError: Private field '#count' must be
//declared in an enclosing class
Notice the error happens at parse time, not at runtime. The engine rejects the code before running it. That’s a much stronger guarantee than the underscore convention ever gave us.
How do you expose the value then?
The field is private, but the class can offer controlled access. A getter works well here:
class Counter {
#count = 0
increment() {
this.#count++
}
get value() {
return this.#count
}
}
const counter = new Counter()
counter.increment()
counter.value //1
Outside code can read the count, but it can’t set it to some arbitrary value. The only way to change it is through increment(). That’s the whole point: the class controls its own state.
One pitfall
A private field must be declared in the class body before you use it. You can’t create one on the fly inside a method:
class Counter {
increment() {
this.#count++ //SyntaxError, #count was never declared
}
}
Regular properties can appear whenever you assign them. Private fields can’t. If you see a syntax error mentioning a private name, check that the #field = ... declaration exists at the top of the class.
This is part of the class fields proposal, which you can use since Chrome 72 and Node 12. It later landed in the language standard with ES2022, so every modern browser supports it today.
Related posts about js: