Data Encapsulation - Private Class Fields and Methods
In this lecture, we will now implement truly private class fields and methods. So let's explore this new feature of JavaScript. When it comes to class fields, there are actually eight different kinds of fields and methods. But in this lecture, we will focus our attention on:
We can think of a field as a property that will be on all instances. That's why we can also call this a public instance field. In our BankAccount
class, for example, the fields locale
and _movements
are two properties that will be on all instances that we create with this class. Because we do not pass any of the values for these fields when we create a new instance, they will be set to the default values that we have defined in the constructor function. Hence the empty array and the language will always be set for these instances.
class BankAccount {
// Public fields
_movements = []; locale = navigator.language;
constructor(owner, currency, pin) {
//...
}
deposit(val) {
this._movements.push(val);
}
//...
}
Now, if you reload the page, you will notice that the result is the same as before. We still have the locale
and _movements
properties on all instances. But now they are actually public fields. But in our final object, it doesn't make any difference. Because, in the end, they will be present in all the instances we are creating through the class.
Note, however, that these public fields will not be on the prototype. The methods we defined will be, but not the fields. Also, the fields are still referenceable via the this
keyword.
With private class fields, we can make sure that properties are only accessible from inside the class. Let's start by finally making the movements array private. For that, all we have to do is to add the #
symbol in front of the field name. And now, if we try to access the movements array from outside the class, we will get an error because the field is now truly private.
class BankAccount {
// Public fields
locale = navigator.language;
// Private fields
#movements = [];
constructor(owner, currency, pin) {
//...
}
getMovements() {
return this.#movements; }
deposit(val) {
this.#movements.push(val); }
//...
}
If you now reload the page you should have something like this:
So, if you now try to access the movements array, you will get an error
console.log(noah.#movements); // Error
This is simply JavaScript telling you that you cannot access a private field from outside the class.
The next candidate that we have to make truly private is the pin
property. However, this time the situation is a bit different because we are actually setting the pin property based on the value that we pass to the constructor function. And we cannot define a field in the constructor function. So, what we have to do is first to define the field outside of the constructor without setting any value.
class BankAccount {
// Public fields
locale = navigator.language;
// Private fields
#movements = [];
#pin; // Will be set to undefined
constructor(owner, currency, pin) {
//...
}
//...
}
And then, we can redefine the pin property inside the constructor function.
class BankAccount {
// Public fields
locale = navigator.language;
// Private fields
#movements = [];
#pin;
constructor(owner, currency, pin) {
this.#pin = pin; // highlight-end
//...
}
//...
}
There is nothing new to say about public methods. They are just methods that we can call from outside the class. And all the methods that we have defined so far are public methods.
class BankAccount {
// Public fields
locale = navigator.language;
// Private fields
#movements = [];
#pin;
constructor(owner, currency, pin) {
this.#pin = pin;
//...
}
// Public methods
getMovements() { return this.#movements; } deposit(val) { this.#movements.push(val); }
//...
}
Private methods, as we already discussed earlier, are very useful to hide the implementation details of a class. And that's why in the previous lecture, we already made the approve loan (_approveLoan
) method protected.
To make this method truly private, the syntax is the same as private fields.
class BankAccount {
// Public fields
locale = navigator.language;
// Private fields
#movements = [];
#pin;
constructor(owner, currency, pin) {
this.#pin = pin;
//...
}
// ..
// Private methods
#approveLoan(val) { return true; }
requestLoan(val) {
if (this.#approveLoan(val)) { this.deposit(val);
console.log(`Loan approved!`);
}
}
//...
}
To finish, we talked about these four types of fields and methods. Besides these four types, there is also the static version of these fields and methods. That's why at the beginning of this lecture, I mentioned that there are eight types of fields and methods. We've actually already used static public methods before. That works by simply adding the static
keyword in front of the method name.
class BankAccount {
// Public fields
locale = navigator.language;
// Private fields
#movements = [];
#pin;
constructor(owner, currency, pin) {
this.#pin = pin;
//...
}
// ..
// Private methods
#approveLoan(val) {
return true;
}
requestLoan(val) {
if (this.#approveLoan(val)) {
this.deposit(val);
console.log(`Loan approved!`);
}
}
// Static methods
static myStaticMethod() { console.log(`Used as helper function`); }}
They are usually used as helper functions because they are not available on the instances but only on the class itself.