The community is working on translating this tutorial into Uzbek, but it seems that no one has started the translation process for this article yet. If you can help us, then please click "More info".
getters & setters
In the previous article on class fields, we saw how we could store data on a class by declaring a field and then access it on instances of the class very easily. For instance, we stored and used a "name" field like this:
class Dog
{
name = "Dog Doe";
}
let dog = new Dog();
dog.name = "John Dog";
alert(dog.name);
Notice how I declare the name field inside the class, and then later change the value from the instance of the Dog class we create. Easy and simple!
However, sometimes you don't want the access to your fields to be this easy. For instance, in the example above, I, as the consumer of the class, could set the name to anything from a weird number to an empty string - there's really no way to control what happens to this field.
But if we apply the concept of getters and setters, we, as the creators of the class, can take this control back. With getters and setters, you essentially add a layer of functionality between the field and the consumer of the class, which allows you to fully control what happens with the field. This is often referred to as data encapsulation.
getters
Both getters and setters are essentially just functions, but they are prefixed by the keywords get and set, to convey their special meaning. Let's start by looking at the getters - here's how it looks in its most basic form:
class Dog
{
#name = "Dog Doe";
get name()
{
return this.#name;
}
}
First of all, notice how I have changed the name of the field to #name. This indicates, as we learned in the article on fields, that the field is now private, basically preventing access to it from outside of the class.
We then declare a method called name(), prefixed with the get keyword. Inside the method, we return the value of the #name field. Because the name() method is declared with the get keyword, we can access it like it's a property, outside of the class:
class Dog
{
#name = "Dog Doe";
get name()
{
return this.#name;
}
}
let dog = new Dog();
alert(dog.name);
We have now defined a getter, but no setter, which basically makes our field read-only. You will see this if you try to change the value of the field - nothing will happen:
class Dog
{
#name = "Dog Doe";
get name()
{
return this.#name;
}
}
let dog = new Dog();
dog.name = "John Dog";
alert(dog.name); // Still "Dog Doe"
If we want to be able to change the value of #name outside of the class, we now need to define a setter as well.
setters
So let's add a setter to the class. It's prefixed by the set keyword, and unlike the get method, it takes a parameter, which is the value being assigned to the field. You can call it whatever you like - in my example, I have called the parameter "val":
class Dog
{
#name = "Dog Doe";
get name()
{
return this.#name;
}
set name(val)
{
this.#name = val;
}
}
let dog = new Dog();
dog.name = "John Dog";
alert(dog.name); // John Dog
As you can see, we now have the ability to assign a new value to the #name field from outside the class - whenever we do that, the set name() method is called, where we can assign the new value to the field.
Adding logic
One of the biggest advantages of using getters and setters is the ability to add logic inside the methods, allowing you to do processing of the values before returning and/or assigning them. This is especially useful for the setter, where you can do validation of the value before assigning it to the field, but it can also be used for the getter, for instance to do formatting of the returned value before returning it.
With that in mind, I have created a new version of the example above, where we add logic to both the getter and setter, to show you how powerful this feature is:
class Dog
{
#name;
get name()
{
let arr = this.#name.split(' ');
arr = arr.map(function(part)
{
return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
});
return arr.join(" ");
}
set name(val)
{
val = val.trim();
if(val == '')
throw new Error("Empty value not allowed!");
this.#name = val;
}
}
let dog = new Dog();
dog.name = prompt("Please enter dog name:");
alert("Name of dog: " + dog.name);
This example is a bit more complicated than it has to be, but I decided to show you something useful instead of something simple. I use several techniques and methods described elsewhere in this tutorial, but if you haven't read about them yet, don't worry - we'll cover it later on.
In the get method, we ensure that the name is formatted properly. It looks a bit complicated, but we simply split the name into parts, e.g. first name and last name, and then we make sure that, for each part, the first character is in UPPERCASE and that the rest of the characters are in lowercase. So, if people enter the name as "jOHN DoE", it would still come out as "John Doe". We use several array and string methods to accomplish this - read more about them in the articles on arrays and strings.
In the set method, we do some very simple validation by checking if the user has submitted an empty value, and if so, we throw an error (more on errors and the process of throwing them later on). By throwing an error, we prevent the #name field from being assigned. You could easily add more validation, like checking if the user has submitted a first name and a last name etc.
Summary
The special get and set methods allow you to gain complete control of a class field. For the consumer of the class, it still appears as a simple field, which they can access like any other field, but behind the scenes, the get and set methods are called, allowing you, the creator of the class, to manipulate the value on the way in and out of the class.