Comments

JavaScript properties, getters and setters

The concept of getters and setters is quite common among popular languages in use today. Have you ever needed to ensure that a given action is always performed before or after assigning some value to an object’s property? This is possible with setters. You can also perform any sort of processing when retrieving the value of a property by using getters (e.g., formatting). One way to implement getters and setters is to use regular methods, like it’s done in Java, but since EcmaScript5, we have a much more elegant and practical alternative available. Well, let’s get started!

Data properties

In JavaScript, objects are collections of properties and you probably have used them to store data before. A property is simply a key-value pair. Here’s an example:

const options = {
    timeout: 5000,
    retries: 3,
    ip: '192.168.1.20',
    port: 4001,
};

Properties in an object can be easily set and read, like this:

artist.name = 'Carice Anouk van Houten';

console.log(artist.name); //=> "Carice Anouk van Houten"

Again, this is definitely nothing new if you already had some experience with JavaScript. To make a distinction between these regular properties and the other type that we will see in this article, we can call the former “data properties”.

Getters and setters: the old way

Before ES5, the only way we had to implement getters and setters was by using regular methods. Like in the example below:

const config = {
    _port: 4001,
    setPort: function(port) {
        // Port must be a number
        if (typeof port !== 'number') return;
        // Port must be within the accepted range
        if (port < 1024 || port > 65535) return;

        this._port = port;
    },
    getPort: function() {
        return this._port;
    },
};

I’ve kept the function keyword in the above example to make it clear that there’s no magic here. Only normal methods. We could have achieved the same effect by using the ES2015’s method definition syntax, which is shorter and somewhat more elegant:

const config = {
    _port: 4001,
    setPort(port) {
        // ...
    },
    getPort() {
        // ...
    },
};

The usage of this kind of getters and setters is quite simple:

config.setPort(8080);
console.log(config.getPort()); //=> 8080

Incrementing a value would be done like this:

item.setQuantity(item.getQuantity() + 1);

Which looks really verbose when compared with incrementing a property directly:

item.quantity += 1;
// Or even simpler...
item.quantity++;

Getters and setters: why?

In OO languages like Java, C++ or C#, it’s possible to expose the members of a class at different levels by using access modifiers (such as public, protected and private). This serves to implement an important concept of OOP called data hiding. A very common use of getters and setters on these languages is to expose members of a class in a safe way. For example, you could implement only a getter to make sure that a private member would never be changed from outside the class. A setter, in its turn, can be used to validate the supplied values and preserve the integrity of instances of that class.

How about  JavaScript?

Well, in JavaScript, things are a bit different. As of the time of writing this article, there are no access modifiers in JS. Currently, the best option that we have is to use symbols, which give us some level of privacy when used as keys. Some people don’t like them, though. Especially because it’s easy to break this privacy by using reflection. I don’t consider this to be a real issue, since languages that have reflection usually allow programmers to break the encapsulation in some way. The problem I see in symbols is that they’re not so practical to use as a simple private accessor would be. The good news is that an EcmaScript proposal that includes private fields is already in stage 3. And they will be immune to any sort of reflection.

In the example of the previous section, we didn’t use symbols or any other trick to simulate a private member. The _port property has nothing special per se. It’s just a regular data property and, as such, can be set or read directly, completely bypassing the getter and setter that we’ve created. Since explaining symbols and other encapsulation techniques is not the purpose of this article, I don’t implement private properties in its code examples. Just keep in mind that getters and setters are commonly (but not always) used to provide access to encapsulated members. Finally, note the underscore preceding the name of the _port property. It’s a de facto convention among JS developers, indicating that a given property is private and shouldn’t be accessed from outside the class where it’s defined.

Getters and setters: the new way (Accessor properties)

Accessor properties are the modern way of defining getters and setters. When it comes to their definition, they’re not that different from our previous example, but it’s their usage that is really different. First, let’s adapt our code to use accessor properties:

const config = {
    _port: 4001,
    set port(port) {
        // Port must be a number
        if (typeof port !== 'number') return;
        // Port must be within the accepted range
        if (port < 1024 || port > 65535) return;

        this._port = port;
    },
    get port() {
        return this._port;
    },
};

As you can see, only two things have changed in this example:

  • Instead of having setPort and getPort methods. We use the set and get syntaxes along with a single name for the property (“port”, in this example)
  • We don’t need the function keyword here. In fact, if you try to use it, you’ll get a SyntaxError.

How to use accessor properties?

If the definition of an accessor property is quite similar to how we create old school getters and setters, their usage has no visible difference from how we use regular data properties:

config.port = 8080;
console.log(config.port); //=> 8080

And here’s how an increment expression looks like:

timer.seconds += 1;

Yes. It’s that simple. =)

Conclusion

  • Objects are collections of properties
  • A regular property is just a key-value pair and can be called a “data property”
  • You can implement getters and setters as normal methods. This is the “old school” way of doing so in JavaScript.
  • Getters and setters are normally used to provide access to private members of a class.
  • Accessor properties can behave exactly like old-regular-method-based getters and setters, but their usage is designed to make them look like data properties.

Do you want to become a JavaScript master? If so, you need to check out Mosh’s JavaScript course. It’s the best JavaScript course out there! And if you liked this article, share it with others as well!

JavaScript hacker, front-end engineer and F/OSS lover.
Tags: , , ,

Leave a Reply

Connect with Me
  • Categories
  • Popular Posts