Da Fish in Sea

These are the voyages of Captain Observant

Encapsulation in JavaScript

| Comments

Background

One of the common misunderstandings about JavaScript is that it does not provide encapsulation, and therefore is not fully capable of Object Oriented Programming. While EcmaScript 3 does not have syntax to specify which members of an object are private or read-only, this is addressed in EcmaScript 5, with the new Object API. However even without the new syntax, it is perfectly possible to achieve encapsulation in JavaScript objects.

The key is understanding how scope works in JavaScript. The main thing to keep in mind is that JavaScript is function scoped: variables are visible anywhere within a function, including within functions which are defined within that function. This is the property of ‘closure’.

The other important thing to understand is that JavaScript does not have Classes, but rather constructor functions. So when you say,

1
var dog = new Dog();

What really happens is that the Dog constructor function gets called, with ‘this’ set to the prototype of the constructor. The prototype is simply another object, which defaults to a plain old Object, but can be specified in code, eg.

1
2
3
4
Dog.prototype = {
        bark: function () {
    }
}

The effect of this is that the dog object will be able to use members which exist in the prototype object, so dog.bark() will work.

Encapsulating State

So how do we get ‘private’ members in objects? The trick is to use the closure property of the constructor function itself, and define some accessor methods within it:

1
2
3
4
5
6
function Dog (name) {

    this.getName = function () {
         return name;
    }
}

So now your dogs can be given a new name when they are created, but the name cannot be directly accessed or changed from the outside.

1
2
3
var fido = new Dog("Fido");
console.log(fido.getName());//"Fido"
console.log(fido.name);//undefined

If you want to make a private variables which are not arguments to the constructor, you can do that by simply declaring them at the top of the constructor function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Dog (name) {

    var age = 0;

    this.getName = function () {
         return name;
    }

    this.getAge = function () {
         return age;
    }

    //example of setter:
    this.setAge = function (newAge) {
          age = newAge;
    }
}

So we could continue in this way, and declare all the methods of the Dog inside the constructor function, and enjoy the benefits of having encapsulated properties. That would work fine, but the downside is that these functions are being re-created every time the constructor is called. This is a waste of memory.. not sure how much of an issue this is in reality unless you’re making thousands of them, but it is something to avoid if possible.

Preventing Memory Loss (What memory problem?)

While it is not possible to completely avoid some duplicate functions, the memory used can be mitigated as follows: if you make getters/setters for all your properties that you want to be private, then you can define the rest of the methods on the prototype to use the getters/setters. This way the only duplicated functions are the getters/setters, which should be very lightweight (couple lines each), and shouldn’t result in memory issues, unless you’re making a particle system or something.

Here is an example of this:

1
2
3
4
5
//...after previous code

Dog.prototype.getAgeInDogYears () {
    return this.getAge() * 7;
}

So this way you have the protection of encapsulated state, and avoid duplicating all your methods in every instance, thanks to prototypal inheritance. Now getters & setters may be distateful to you, but it is the best solution to this issue I can see, and it has the additional benefit of adding a layer of abstraction to your code, so you could change the way the state values are implemented, without changing a bunch of references to properties throughout the class.

Inheriting

As always, for inheritance to work with non-empty constructors, it is important that you explicitly call the prototype’s constructor:

1
2
3
4
5
6
function Daschund(name) {
    //...
    Dog.call(this, name);
}

Daschund.prototype = new Dog();

The Behaviour Encapsulation Dilemma

So we have private variables which can only be accessed by the get/set functions we declared in the constructor. But what about private functions? They too can be declared in the constructor function as inner functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Dog (name) {

    var age = 0;

    //private function
    function scratch() {
        console.log("scratch");
    }

    this.getName = function () {
         return name;
    }

    this.getAge = function () {
         return age;
    }

    //example of setter:
    this.setAge = function (newAge) {
          age = newAge;
    }
}

The limitation here is that while such inner functions can be called by other functions which are defined within the constructor, they cannot be called by prototype methods. You could put all methods which need to access the private methods also inside your constructor, but then you’re back to the memory issue.

A way around this is to wrap the entire definition of the constructor and prototype in a closure function, which contains the private methods. This is basically the module pattern, and indeed if you are defining a constructor function inside a module, you can take advantage of the hiding effect of the module.

But there is an important gotcha here: these methods (and any other vars they access) are static: they are shared between all instances created with the constructor function contained within the same module. So you need to be aware of this. This is not a problem if you have static constants and ‘pure functions’ without side-effects encapsulated in your module. But it is potentially dangerous that one instance could call a method which could affect other instances… a kind of state contamination which is best avoided. Static and stateful just don’t go well together.

There’s also the issue that the module pattern may be too effective in preventing behaviour modification, and undermine the dynamic nature of the language.

Perhaps perhaps private instance methods are not absolutely necessary, and may be detrimental. AFAIK Smalltalk does not have them either, and takes a similar approach to that outlined here, of encapsulating state by only exposing methods (strictly speaking, ‘messages’, in Smalltalk), not properties. And Smalltalk is the archetypal Object Oriented language.

To be honest, I’ve never really found myself feeling the need for all this privacy, but I’m excited to have found a way to do it without using a third party Class system for JavaScript.

Comments