Encapsulation in JavaScript

The topic of this article is encapsulation in programming. We'll cover the following:

  • what encapsulation is
  • why encapsulation is useful
  • how to implement encapsulation correctly in JavaScript

Encapsulation is a powerful technique that helps you avoid bugs and modularize your programs for easier development. It is especially important when you're working with other developers or if you are authoring an open source library for others to use.

What is encapsulation?

There are various definitions for encapsulation floating around the internet. However, I think the most useful one is the following:

Encapsulation is a programming technique for restricting direct access to a component's internal state.

Direct access means that you can't directly reassign an encapsulated variable. You can only change it via the public API of the component. If it isn't obvious what I mean by this keep reading - we'll look at some examples later in this article.

Why encapsulation?

You can use encapsulation to protect a component from getting to an invalid state. It does this by protecting access to the data that defines the component's state.

What is an invalid state?

What counts as an invalid state depends on the component, of course, but let's say we have a clock component that has two methods: getTime() and advance().

Let's say we call clock.getTime() and it returns 9:60. Obviously, that is an invalid state for a clock to be in. There are only 60 minutes in an hour. 9:59 should be followed by 10:00.

How do you prevent components from getting to invalid states?

To ensure that the clock will never get to this invalid state we add some checks in the advance() method to make sure that the minutes and the hours are handled correctly. It could look something like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Clock {
constructor() {
this.minutes = 9;
this.hours = 59;
}
advance() {
if (this.minutes < 59) {
this.minutes += 1;
} else {
this.minutes = 0;
if (this.hours < 23) {
this.hours += 1;
} else {
this.hours = 0;
}
}
}
getTime() {
const hours = this.hours < 10 ? `0${this.hours}` : this.hours;
const minutes = this.minutes < 10 ? `0${this.minutes}` : this.minutes;
return `${hours}:${minutes}`;
}
}

Now, here's the catch: for this to work we have to hide this.hours and this.minutes from the user. Otherwise they can break this code by simply setting the minute property to 60. The purpose of encapsulation is to make this impossible.

1
2
3
4
var clock = new Clock();
clock.getTime() // -> 09:59
clock.minute = 60;
clock.getTime() // -> 09:60

By letting the user only change the state via a public API we can ensure that the component cannot get to an invalid state unless we have a bug in our API. This will greatly diminish the number of possible bugs, which should be one of the primary concerns of any programmer.

Why does this matter?

While the example above may seem contrived, it's good to remember that the best way to avoid bugs is to make them impossible. People don't generally care how you intent your code to be used. They want to get their job done and if modifying the internal state makes it easier then they'll do just that. And when your component breaks they'll most likely blame you regardless of whether they used your code "incorrectly". Let's also keep in mind that, given enough time, you will also forget how to correctly use your own code.

How to implement encapsulation

Most programming languages offer some way of encapsulation. Usually, its classes with private properties and methods. The keyword here is private. It means that those properties can only be accessed from within the class itself and are therefore not accessible to outside users.

There are many ways of creating objects in JS but most of them don't support encapsulation. Here, I list a few standard practices in Javascript and explain why they are or aren't useful for encapsulation.

Please note that lacking support for encapsulation doesn't mean that a given technique is always incorrect or that you should never use them. We are only looking at these from the perspective of encapsulation.

ES6 classes

The ES6 version of Javascript has a class syntax. You saw it used earlier for the Clock class. You can declare a class by using the class keyword, give it a constructor and declare some variables. The problem is that there is no way to make these variables private. Therefore, everything you attach to this can be accessed by anybody.

Binding functions to the Prototype

Then, there's the ES5 way where you attach the methods to the object's prototype and members to the object itself. This is the same as ES6 classes but without the syntactic sugar. It, too, is utterly useless for encapsulation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var Clock = function() {
this.minutes = 9;
this.hours = 59;
};
Clock.prototype.advance = function() {
if (this.minutes < 59) {
this.minutes += 1;
} else {
this.minutes = 0;
if (this.hours < 23) {
this.hours += 1;
} else {
this.hours = 0;
}
}
}
Clock.prototype.getTime = function() {
var hours = this.hours < 10 ? '0' + this.hours : this.hours;
var minutes = this.minutes < 10 ? '0' + this.minutes : this.minutes;
return hours + ':' + minutes;
}

Using underscore ( _ ) to mark private variables and methods

Prepending private methods and properties with an underscore (_) is very common. This is not encapsulation, it's just a naming convention. Though better than nothing, I find it very difficult to stick to conventions like this. Moreover, users of your code may not be familiar with this convention.

Closures

The only way to do proper encapsulation in Javascript is to use closures. A closure is created when a function accesses a variable defined outside the function itself.

1
2
3
4
5
6
7
8
9
var a = 0;
function next() {
a += 1
return a;
}
next(); // -> 1
next(); // -> 2
next(); // -> 3

To use this feature for encapsulation you can create a function that returns another function. Declare the private variables in the outer function and access them from the inner function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function makeCounter() {
// we declare variable "a" inside makeCounter()
// but outside next()
var a = 0;
return function next() {
// now we can access and reassign "a" inside next()
// "a" is hidden from the outside world
a += 1;
return a;
}
}
var next = makeCounter();
next(); // -> 1
next(); // -> 2
next(); // -> 3

Let's revisit the clock component from above and implement it using a closure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function makeClock() {
var minutes = 59;
var hours = 9;
var toTwoDigits = function(number) {
return number < 10 ? '0' + number : number;
};
return {
advance: function() {
if (minutes < 59) {
minutes += 1;
} else {
minutes = 0;
if (hours < 23) {
hours += 1;
} else {
hours = 0;
}
}
},
getTime: function() {
return toTwoDigits(hours) + ':' + toTwoDigits(minutes);
}
}
}
var clock = makeClock();
clock.getTime(); // -> 09:59
clock.minutes = 60; // this has no effect on getTime()
clock.getTime(); // -> 09:59
clock.advance();
clock.getTime(); // -> 10:00

Common pitfalls

Let's see how we can break encapsulation even when using closures:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function makePoint() {
var position: {x: 1, y: 2};
return {
moveLeft: function() {
// make sure position can never be less than 0
if (position > 0) {
position.x -= 1;
}
},
getPosition: function() {
return position;
}
};
}
var point = makePoint();
point.moveLeft();
var position = point.getPosition(); // -> {x: 0, y: 2}
position.x = -1;
point.getPosition(); // -> {x: -1, y: 2}

The problem here is the getState function. You see how we just hand out the internal state to whomever asks for it. This immediately breaks encapsulation. One should only be able to modify the inner state by calling the object's methods. Now that we've leaked the inner state it can be set to an invalid state from the outside.

This leads us to an important rule:

Public methods of an encapsulated component should only return representations of the component's internal state, not references to the state itself.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function makePoint() {
var position: {x: 1, y: 2};
return {
moveLeft: function() {
if (position > 0) {
position.x -= 1;
}
},
getPosition: function() {
// return a copy of position
return position.map(function (x) {
return x;
});
}
};
}
var point = makePoint();
point.moveLeft();
var position = point.getPosition(); // -> {x: 0, y: 2}
position.x = -1;
point.getPosition(); // -> {x: 0, y: 2}

In this corrected version we return a copy of the object's position. This way we don't leak that internal state and can prevent it from being modified incorrectly.

Conclusion

Encapsulation is a technique for restricting access to a component's internal state. This is useful when you want to prevent the component from getting to an invalid state. The correct way of implementing encapsulation in Javascript is by using closures.

Notice that we didn't talk about when to use encapsulation. This is too difficult a question to fit in the scope of this article but we will definitely explore it in future tutorials.