In modern JavaScript (ES2022+), Private Methods are a critical feature of the class syntax that allows developers to define functions that are only accessible from within the class itself. By prefixing a method name with the hash symbol #, the JavaScript engine enforces a strict boundary, preventing these methods from being called, viewed, or even detected by external scripts or inheriting subclasses.
This mechanism is the cornerstone of Encapsulation, enabling you to hide the “how” (internal implementation details) while only exposing the “what” (the public API).
1. Syntax and Implementation
To create a private method, you must include the # prefix in the method definition within the class body. Like private fields, they cannot be declared or added dynamically outside the class definition.
class SecureUploader {
#connectionStatus = 'IDLE';
// Public method: The "Interface"
async uploadFile(file) {
this.#validateFile(file); // Internal check
await this.#establishSocket(); // Internal setup
console.log(`Uploading ${file.name}...`);
}
// Private method: Logic hidden from the user
#validateFile(file) {
if (file.size > 1024 * 1024) {
throw new Error("File too large.");
}
}
// Private method: Complex internal state management
async #establishSocket() {
this.#connectionStatus = 'CONNECTING';
// ... networking logic ...
}
}
const uploader = new SecureUploader();
uploader.uploadFile(myFile); // ✅ Works
// uploader.#validateFile(myFile); // ❌ SyntaxError: Private field '#validateFile' must be declared in an enclosing class
2. Static Private Methods
You can combine the static and # keywords to create private methods that belong to the class constructor rather than the instance. This is useful for utility functions that the class needs internally but shouldn’t be exposed as part of its public utility suite.
class Database {
static #connectionCount = 0;
static connect() {
this.#logConnection();
this.#connectionCount++;
}
static #logConnection() {
console.log(`Establishing connection #${this.#connectionCount + 1}`);
}
}
3. Interaction with Private Fields and Accessors
Private methods often work in tandem with Private Fields and Private Getters/Setters. This creates a fully encapsulated ecosystem where the internal “engine” of the class is shielded from the “dashboard” (public methods).
class UserProfile {
#age;
constructor(age) {
this.#age = age;
}
get isAdult() {
return this.#validateAge();
}
// Private Method for validation
#validateAge() {
return this.#age >= 18;
}
}
4. Private Accessors (Getters and Setters)
You can also make Getters and Setters private. This is useful for providing a controlled way for other internal methods to read or modify private fields while still keeping that logic hidden from the outside.
class Database {
#retryCount = 0;
get #isOverloaded() {
return this.#retryCount > 5;
}
connect() {
if (this.#isOverloaded) {
console.log("System overloaded. Aborting.");
return;
}
// connection logic...
}
}
5. Interaction with Inheritance
A defining characteristic of private methods is that they are not inherited. Even if a subclass extends a parent, it cannot call the parent’s private methods. This prevents “Fragile Base Class” syndromes, where changes to internal helpers in a parent class accidentally break child classes that were relying on those helpers.
class Parent {
#internalLogic() {
console.log("Parent's secret logic");
}
callSecret() {
this.#internalLogic(); // ✅ Parent can call its own private method
}
}
class Child extends Parent {
attemptCall() {
// this.#internalLogic(); // ❌ SyntaxError: Private field must be declared in an enclosing class
}
}
6. Technical Nuances and Constraints
No “Protected” Scope
JavaScript does not currently have a “protected” keyword (accessible to subclasses but not the public). Private methods are hard private—they are not accessible even to classes that extend the parent class.
class Parent {
#hidden() { console.log("Secret"); }
}
class Child extends Parent {
reveal() {
// this.#hidden(); // ❌ Error: #hidden is not accessible here
}
}
The “In” Operator for Branding
You can check if an object has a specific private method using the in operator. This is a secure way to verify if an object is an instance of a specific class before attempting to interact with its internals (a pattern known as “branding”).
class Validator {
#privateMethod() {}
static isValidator(obj) {
return #privateMethod in obj;
}
}
