Setters and Getters
In this lecture, we will talk about a feature that's common to all objects in JavaScript: setters and getters. Every object can have setter and getter properties in JavaScript. And we call them accessor properties, while the other properties we have seen so far are called data properties. So what are setters and getters? As the name suggests, they are functions or methods that get and set a property's value. But on the outside, they look like normal properties. So let's start by seeing how this works in practice with a simple object literal.
Let's consider the following object literal:
const account = {
owner: 'Christopher Simpson',
movements: [200, 450, -400, 3000, -650, -130, 70, 1300],
};
Now let's say we want a method to get the total amount of deposits in the movements
array. We could do this by creating a method on the object:
const account = {
owner: 'Christopher Simpson',
movements: [200, 450, -400, 3000, -650, -130, 70, 1300],
totalDeposits() { // Do something },};
To transform this method into a getter, we simply prepend the get
keyword to the method name.
const account = {
owner: 'Christopher Simpson',
movements: [200, 450, -400, 3000, -650, -130, 70, 1300],
get totalDeposits() { return this.movements .filter((mov) => mov > 0) .reduce((acc, mov) => acc + mov, 0); },};
And now, instead of calling the method like a function, we can access it like a property:
console.log(account.totalDeposits); // ==> 5020
This can be very useful when we want to read something as a property but still, need to do some calculations. So this is how we can create a getter. Now let's see how we can create a setter. Let's say we want to create a method that sets the latest movement in the movements
array.
const account = {
owner: 'Christopher Simpson',
movements: [200, 450, -400, 3000, -650, -130, 70, 1300],
get totalDeposits() {
return this.movements
.filter((mov) => mov > 0)
.reduce((acc, mov) => acc + mov, 0);
},
set latest(mov) { this.movements.push(mov); },};
Now, how do we call this method? Well, if it was a regular method, we would call it like this:
account.latest(40);
But since it's a setter, we can't call it like a regular method. Instead, we need to set it like a property:
account.latest = 40;
If we now log the movements
array, we can see that the latest movement has been added:
console.log(account.movements);
So, in a nutshell, this is how setters and getters work for any regular object in JavaScript. However, classes also have setters and getters. And they do indeed work in the same way. So let's see how we can create setters and getters in classes. Let's use our Person
class from the previous lectures as an example.
class Person {
constructor(firstName, lastName, birthYear, profession) {
// ...
}
calculateAge() {
console.log(2024 - this.birthYear);
}
}
const william = new Person('William', 'Park', 1992, 'Designer');
Let's say we want to create a getter that returns a person's full name.
class Person {
constructor(firstName, lastName, birthYear, profession) {
// ...
}
calculateAge() {
console.log(2024 - this.birthYear);
}
get fullName() { return `${this.firstName} ${this.lastName}`; }}
Now, if we want to access the full name of a person, we can simply do this:
console.log(william.fullName); // ==> William Park
With this, you can see that a getter is indeed just like any other regular method that we set on the prototype. And in fact, we can see that on the prototype of the william
object:
This was a very simple use case for a getter. But setters and getters can actually be very useful when we want to do some data validation. As an example, let's try some validation with a person's profession. Let's say we want to check if a person's profession is a string and if it differs from an empty string. And if it's not, we want to log an error message.
class Person {
constructor(firstName, lastName, birthYear, profession) {
// ...
this.profession = profession; }
calculateAge() {
console.log(2024 - this.birthYear);
}
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
set profession(value) { console.log(value); if (typeof value !== 'string' && value !== '') { console.log('Invalid profession'); } else { this.profession = value; } }}
What's important to understand here is that we are creating a setter for a property name that already exists. What this means is that each time we set a new value for the profession
property in the constructor, the setter (profession
) will be called. Hence, the value
parameter in the setter will be the new value that we set for the profession
property. So let's try this out:
Hmmm, this doesn't seem to work 🤔. We are actually seeing the profession
property being logged, but now we are getting a really cryptic error message. Let's try to debug this.
What's actually happening here is that there is a conflict. Both the setter function and the constructor are trying to set the profession
property. Hence resulting in the weird error message we just saw. So, what we need to do is to create a new property name, and the convention for this is to add an underscore to the property name. So let's do that:
class Person {
constructor(firstName, lastName, birthYear, profession) {
// ...
this._profession = profession;
}
//...
set profession(value) {
console.log(value);
if (typeof value !== 'string' && value !== '') {
console.log('Invalid profession');
} else {
this._profession = value; }
}
}
⚠️ Again, it is just a convention, not a JavaScript feature. It is just a different variable name to avoid the naming conflict. However, doing this creates a new property on the object. So if we now log the william
object, we can see that there is a new property called _profession
:
And now, if we try to log the profession
property, we will get undefined
:
console.log(william.profession); // ==> undefined
Hence to fix this, we need to create a getter for the profession
property. And in the getter, we can simply return the _profession
property:
class Person {
constructor(firstName, lastName, birthYear, profession) {
// ...
this._profession = profession;
}
//...
set profession(value) {
console.log(value);
if (typeof value !== 'string' && value !== '') {
console.log('Invalid profession');
} else {
this._profession = value;
}
}
get profession() { return this._profession; }}
console.log(william.profession); // ==> Designer
Let's try to create another user just to test our validation. Let's create a user with an invalid profession:
const maggie = new Person('Johnston', 'Maggie', 1992, 23);
As you can see, we get the expected error message. Now, if we log the maggie
object, we can see that the _profession
property does not exist:
But now, if we add a valid profession, then we indeed get the _profession
property:
const maggie = new Person('Johnston', 'Maggie', 1992, 'Lawyer');
console.log(maggie);
And just like before we can access the profession
property:
console.log(maggie.profession); // ==> Lawyer
Nice! So this pattern of creating a new property name with an underscore is important to understand whenever we try to set a property that already exists. To finish, we don't need to use getters or setters, and many people actually don't. However, sometimes it's nice to be able to use these features, especially when we want to do some data validation.