logo le blog invivoo blanc

Data encapsulation in JavaScript: getters and setters

10 August 2023 | Front-End, Java | 0 comments

Data encapsulation in JavaScript: getters and setters

When building larger JavaScript applications, there’s soon the need to split it into modules linked by clear contracts. When it comes to long term maintenance, a way to keep compatibility upon interface changes needs to be provided. In order to do that, object oriented languages rely on encapsulation or information hiding implementation details from users of a piece of code, so that it can change without impacting clients.

In this post we’ll talk about Data encapsulation in JavaScript. More specifically, we’ll talk about encapsulating properties behind getters and setters.

Some concepts

JavaScript was designed having the principle of duck typing as basis:

“When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.”

In other words, at it’s core, JavaScript is an object oriented language, but differently from other object oriented languages such as C# and Java, in JavaScript classes are second class citizens and interfaces do not exist. Objects have no types, and thus, code needs to be written to interface to objects that have certain properties and methods. Contracts between modules are based on a set of methods and properties assumed to be present in the exchanged objects.

Encapsulation is fundamental in object oriented programming. It means that a given object should be able to hide details of their inner workings, so that other objects that interact it do not depend on details of its implementation, but on a high level agreed upon interface. This makes lives of developers simpler, both on the side of the provider and on the side of the users, as both know the implementation details can change without breaking client objects.

Why exposing data on an interface is a bad idea

In JavaScript an object is simply a dictionary of property names mapped onto values, when the value of a property is a function, we call it a method. The interface between methods is the set of properties the client code expects to find on an object.

If an object exposes only methods, it is easy to make the interface evolve with the time. A method can check its parameters and react accordingly. It is also possible to create new methods providing new features. And, supporting old ones that adapt the old behavior upon the new one. With properties it is not possible to do that.

In the case of properties, keeping an stable interface is not easy. For, by default, client code can refer to non-existing properties of an object. Writing to a non-existing property will create it. Reading it will return undefined. Client code won’t be able to detect quickly if it is using deprecated properties. Worse, the error may propagate to other parts of code, making detection of the problem harder.

The standard JavaScript API has methods that may be useful in avoiding such problems: it is possible to freeze or seal an object, so that non supported property names cannot be used.

Getter/Setters to save the day

Hiding the details of method implementation is easy.

Getters/setters have been officially introduced to the language in ECMAScript 5.1 (ECMA-262). They are currently supported in all major desktop and mobile browsers.

The basic idea is that it adds syntax to define accessor properties as methods, instead of simple data properties. A getter is defined by the keyword get followed by a function named after the property, taking no arguments and returning the value of the property. A setter is defined by the keyword set followed by a function named after the property taking the new value of the property as a parameter.

The following example illustrates a getter and a setter used to define an accessor property called prop:

var obj = {
  v: 0,
  get prop() {
    return this.v;
  },
  set prop(newValue) {
    this.v = newValue;
  }
};

console.log(obj.prop);
obj.prop = 42;
console.log(obj.prop);

Output:

0
42

One can also create a read-only property by defining a getter without setter:

var obj = {
  get prop() {
    return -1;
  },
};

console.log(obj.prop);
obj.prop = 42;
console.log(obj.prop);

Output:

-1
-1

A property can then be replaced by a couple of methods that in case the implementation of a class change, can react to it, and adapt the class behavior. In the worst-case, it can raise an exception to tell the user that the property is deprecated and shouldn’t be used anymore.

Further reading

I hope you learned something new about designing complex evolutive applications in JavaScript in this post. In this section, I will give you a couple of links to links containing more information.

Introductory articles

This section brings a couple of short articles presenting the concepts and providing an overview of the related APIs:

  1. Property getters and setters.
  2. JavaScript Getters and Setters

More advanced articles

These articles talk about more advanced related topics like the problems brought by getters and setters, and how to solve them.

  1. Why getters/setters is a bad idea in JavaScript
  2. Data encapsulation in JavaScript

Reference documentation

Finally, to really master the topic, the links to the mozilla documentation of the related APIs:

  1. get and set allow one to define getters and setters. Beyond what we explained here. There are other interesting features like the support for dynamically generated property names, and using getters and setters to simulate setting/getting values in an array
  2. seal and freeze allow one to control which properties of an object are modifiable or not and how. It can be used to avoid having clients using deprecated parts of an object’s API.
  3. defineProperty allows one to define getters and setters and at the same time having more control on how these properties are seen by clients and how modifiable they are.