Why hide data
By default, every property of a JavaScript object is public: any code that holds the object can read it and overwrite it. That is convenient, but it lets outside code put an object into a state its author never intended, such as a negative age. Hiding a field and exposing it only through methods lets the class keep control of its own data. This idea is called encapsulation.
Some languages express it with keywords such as public and private, called access modifiers. JavaScript has no public or private keywords. It has a prefix instead.
The underscore convention
For many years JavaScript classes had no real privacy. Programmers began a property's name with an underscore, as in this._name, to tell readers "this is internal, leave it alone". That is only a hint. Nothing stops outside code from reading or changing it:
class Dog {
constructor(name) {
this._name = name;
}
}
let rex = new Dog("Rex");
rex._name = "changed";
console.log(rex._name); // changed
You will still meet this style in older code, but it protects nothing.
True private fields with #
Modern JavaScript (ES2022 and later) has real private fields. Put # in front of the name:
class Dog {
#name;
constructor(name) {
this.#name = name;
}
getName() {
return this.#name;
}
}
The rules are strict:
- Declare the field first. The line
#name;at the top of the class body is required. Usingthis.#namewithout declaring it is aSyntaxError, and a private field can't be created later by assignment the way an ordinary property can. - The
#is part of the name. Always write it asthis.#name. It is not a string, sothis["#name"]doesn't reach it: that is an ordinary property that happens to have#in its name, and it isundefinedhere. - Only the class body can use it. Code outside the class that writes
rex.#namedoesn't compile: it is a syntax error, found before the program runs, and not a runtime failure. A subclass can't use its parent's private field either.
A private field also stays out of sight. Object.keys(rex) gives an empty list, JSON.stringify(rex) gives {} and console.log(rex) prints Dog {}.
Using private fields well
A private field is usually paired with methods that control access to it. A getter method returns the value, and a setter changes it after checking it:
class Dog {
#age = 0;
setAge(value) {
if (value < 0) {
throw new RangeError("age must not be negative");
}
this.#age = value;
}
getAge() {
return this.#age;
}
}
Now no code can give a dog a negative age. The same # prefix works on methods, so #helper() { ... } defines a private method that only the class itself can call.
The exercise, step by step
The starter code declares #name and stores into it in the constructor, then calls rex.getName(). That method doesn't exist yet.
- Declare the method. Below the constructor, inside the class, write an empty
getName() { }. - Return the field. In the body write
return this.#name;. BecausegetNamebelongs to the class, it is allowed to read the private field, and the caller receives the value.
The solution in JavaScript
class Dog {
#name;
constructor(name) {
this.#name = name;
}
getName() {
return this.#name;
}
}
let rex = new Dog("Rex");
console.log("Dog's name: " + rex.getName());
The output is:
Dog's name: Rex
Common mistakes
- Forgetting the declaration. Without
#name;in the class body,this.#name = namefails with aSyntaxErrorsaying the private field must be declared in an enclosing class. - Dropping the
#inside the class.this.nameis a different, public property. A class can have bothnameand#name, and mixing them up leavesgetName()returningundefined. - Reading it from outside.
rex.#nameis a syntax error. Add a method such asgetName()instead. - Expecting privacy from the underscore.
_nameis a naming habit and nothing more.
Key takeaways
- Properties are public by default, and JavaScript has no
privatekeyword. - A name starting with
#is a private field, enforced by the language. - Declare it in the class body, and read and write it only as
this.#nameinside the class. - Pair private fields with getter and setter methods to control how the data is used.