How Private Class Fields Work in JavaScript
JavaScript private class fields, identified by the #
prefix, provide built-in encapsulation by restricting property and
method access strictly to the class body in which they are declared.
This article explains the mechanics of the hash prefix syntax,
demonstrates how it enforces true hard privacy at runtime, and covers
its application across instance fields, static variables, methods, and
accessors.
The Syntax for Private Fields
To define a private field, prefix its name with a #
symbol within the class definition. You must declare private fields at
the top level of the class body before using them.
class BankAccount {
// Declare the private field
#balance;
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount(100);
account.deposit(50);
console.log(account.getBalance()); // 150
// Attempting external access throws a SyntaxError
// console.log(account.#balance); How Hard Privacy Works
Unlike traditional naming conventions (like _variable)
or TypeScript’s private keyword, JavaScript’s hash syntax
enforces true runtime privacy:
- No External Access: Attempting to read or write a
#field outside its defining class results in a syntax error at compile time or a runtime error. - Invisible to Reflection: Private fields do not
appear in
Object.keys(),Object.getOwnPropertyNames(), orReflect.ownKeys(). - No Bracket Notation: Private fields cannot be
accessed dynamically using square brackets (e.g.,
this['#balance']will look for a literal string property named"#balance"rather than the private field). - No Inheritance Access: Subclasses cannot access private fields of a parent class. They belong exclusively to the declaring class scope.
Private Methods, Getters, and Setters
The # prefix extends beyond standard instance variables
to include private methods, getters, and setters.
class User {
#password;
constructor(username, password) {
this.username = username;
this.#password = password;
}
// Private method
#validatePassword(input) {
return input === this.#password;
}
// Private getter
get #maskedPassword() {
return '****';
}
verify(input) {
return this.#validatePassword(input);
}
}Private Static Fields
You can combine the static keyword with the
# prefix to create private class-level properties and
methods accessible only by the class itself.
class Counter {
static #instanceCount = 0;
constructor() {
Counter.#incrementCount();
}
static #incrementCount() {
Counter.#instanceCount++;
}
static get totalInstances() {
return Counter.#instanceCount;
}
}
new Counter();
new Counter();
console.log(Counter.totalInstances); // 2Checking for
Private Fields Using the in Operator
JavaScript allows you to check whether an object possesses a specific
private field using the in operator inside the class scope.
This provides a safe way to test an object’s brand without triggering
errors.
class Person {
#name;
constructor(name) {
this.#name = name;
}
static isPerson(obj) {
return #name in obj;
}
}
const person = new Person('Alice');
const other = { name: 'Bob' };
console.log(Person.isPerson(person)); // true
console.log(Person.isPerson(other)); // false