Most class properties have their private counterparts:
- Private fields
- Private methods
- Private static fields
- Private static methods
- Private getters
- Private setters
- Private static getters
- Private static setters
These features are collectively called private properties. However, constructors cannot be private in JavaScript. To prevent classes from being constructed outside of the class, you have to use a private flag.
Private properties are declared with # names (pronounced "hash names"), which are identifiers prefixed with #
. The hash prefix is an inherent part of the property name — you can draw relationship with the old underscore prefix convention _privateField
— but it's not an ordinary string property, so you can't dynamically access it with the bracket notation.
It is a syntax error to refer to #
names from outside of the class. It is also a syntax error to refer to private properties that were not declared in the class body, or to attempt to remove declared properties with delete
.
class ClassWithPrivateField {
#privateField;
constructor() {;
delete this.#privateField;
this.#undeclaredField = 42;
}
}
const instance = new ClassWithPrivateField();
instance.#privateField;
JavaScript, being a dynamic language, is able to perform this compile-time check because of the special hash identifier syntax, making it different from normal properties on the syntax level.
Note: Code run in the Chrome console can access private properties outside the class. This is a DevTools-only relaxation of the JavaScript syntax restriction.
If you access a private property from an object that doesn't have the property, a TypeError
is thrown, instead of returning undefined
as normal properties do.
class C {
#x;
static getX(obj) {
return obj.#x;
}
}
console.log(C.getX(new C()));
console.log(C.getX({}));
This example also illustrates that you can access private properties within static functions too, and on externally defined instances of the class.
You can use the in
operator to check whether an externally defined object possesses a private property. This will return true
if the private field or method exists, and false
otherwise.
class C {
#x;
constructor(x) {
this.#x = x;
}
static getX(obj) {
if (#x in obj) return obj.#x;
return "obj must be an instance of C";
}
}
console.log(C.getX(new C("foo")));
console.log(C.getX(new C(0.196)));
console.log(C.getX(new C(new Date())));
console.log(C.getX({}));
Note a corollary of private names being always pre-declared and non-deletable: if you found that an object possesses one private property of the current class (either from a try...catch
or an in
check), it must possess all other private properties. An object possessing the private properties of a class generally means it was constructed by that class (although not always).
Private properties are not part of the prototypical inheritance model since they can only be accessed within the current class's body and aren't inherited by subclasses. Private properties with the same name within different classes are entirely different and do not interoperate with each other. See them as external metadata attached to each instance, managed by the class. For this reason, Object.freeze()
and Object.seal()
have no effect on private properties.
For more information on how and when private fields are initialized, see public class fields.