Online Workshop Object Oriented Design in Javascript

There are many ways to use object oriented techniques in JavaScript (JS). Still, a lot of people think JavaScript is not an object oriented language because it does not have the concept of a class (Fortunately this is changing rapidly thanks to the increasing popularity of the language...). In my opinion, the fact that JS does not have a class statement makes JS even more object oriented. While class based languages like Java focus on objects indirectly (you never write objects, you always write classes), JS focuses on objects directly. In JavaScript you deal with real objects instead of classes.
Here I present my favourite way to work with objects in JavaScript. In the process, I hope to convey some of the warm feelings I have for JavaScript.

Objects Literals

One of the most powerful features of JavaScript is that you can use object literals. So, creating an object is much easier in JavaScript than in Java or any other language; compare this (PHP):

class Car { } 
$car = new Car();

And this (JavaScript):

car = {};

My approach to object design in JavaScript centers around the concept of object literals (not prototypes).

Constructors

There are many ways to create new objects in JavaScript:

var a = new Object();
var a = {};
 
function myObj(){}
var a = new myObj;

However, I rather prefer to build objects using a factory method.

Cat = {
    createNew: function(){
        var cat = { "name":"Silvester" }
        return cat;
    }
} 

This object named 'Cat' acts as a class and contains a factory method called 'createNew', that works like a constructor. It assembles an object called 'cat' and returns a reference to this newly created instance.

The 'cat' instance returned by 'createNew' looks like:

{ "name" : "Silvester" }

So, if we ask the cat to print its name; it will print 'Silvester':

alert( cat.name ); //will print "Silvester"

Classes

JavaScript lacks the concept of classes (and to be frankly I am okay with that because you don't need them). I like to use objects to substitute classes, these objects build other objects; for the sake of simplicity I call them builders. Let's see how you can add a method to an object in a builder and then invoke that method from outside the builder:

Cat = {
    createNew: function() {
        var cat = {}; //create an empty object
        cat.makeSound= function(){ //add logic
            alert("meow");
        }
        return cat; //return the fabricate
    }
}
 
var cat = Cat.createNew();
cat.makeSound();

Private properties

Making properties private is easy, because JavaScript has lexical scoping rules. This means that if you declare a variable or function within a certain lexical scope, only functions in that same block of code will be able to access it. This may sound a little abstract, so here is an example:

Cat = {
    createNew: function() {
        var cat = {};
        var sound = "meow"; //sound is local
        cat.makeSound= function(){
            //can reach sound from here
            alert( sound ); 
        }
        return cat;
    }
}
 
 
var cat = Cat.createNew();
cat.makeSound();
//but can't reach sound from here
alert(cat.sound); //fail!

Note that the variable 'sound' is a private property of 'cat'. In contrast to what most people believe about JavaScript it is perfectly possible to create a truly private property like this (actually you can still access it using eval("sound='whaf!'",Cat.createnew); however it's private 'enough' for practical usage, remember that other languages offer reflection techniques to change private properties) The variable 'sound' will not be accessible from outside the 'createNew' function. If you invoke 'makeSound', the property becomes accessible again because 'makeSound' shares the same lexical scope as 'sound'. This way, you mimic the behaviour of a private property.

The reason why people think JavaScript does not support private properties is because you cannot add a private property directly from global scope:

obj._private = "secret!";

Or in class-context:

obj = {
   private property = 1;	
}

This will fail, but if you add the member as a reference within a function, it works:

builder = function() {
   var private = "secret";
   return { tellSecret: function(){
     alert(private); } }
}

The method 'tellSecret', which is public, can access the private member 'private'. The private member though is truly private; you won't be able to access it from outside the builder function.

Note however that private properties are even 'more private' than you might expect; if you add methods in a different scope later on, these methods will not be able to access the private members, even though they are part of the same object. Because most class based languages do not support on-the-fly method definitions; they do not have this distinction.

Inheritance

Instead of creating a brand new 'Cat' object we use the 'Animal' object as our base type, then we add our 'Cat' specific modifications and we are done:

Animal = {
    createNew: function() {
        var animal = {};
        animal.eat = function(){ alert("eating"); }
        return animal;
    }
}
 
Cat = {
    createNew: function() {
        var cat = Animal.createNew(); //Inherits
        var sound = "meow";
        cat.makeSound= function(){
            alert( sound );
        }
        return cat;
    }
}
 
var cat = Cat.createNew();
cat.makeSound();
cat.eat();

In fact, inheritance in JavaScript is more like "we turn an Animal into a Cat" than regular inheritance.

Protected Properties and Methods

Although a protected property is inaccessible from outside, all objects that are part of the same family tree may share the protected components. We create a new object called 'protected' that contains the shared stuff. I like to call this shared object, a bundle. If a bundle is passed to the 'Animal' constructor, the constructor will add new items to the bundle, if not it simply creates a new bundle and the components will behave like private members instead. However if the 'Cat' wishes to make use of the protected bundle, it simply passes the shared object to 'Animal':

Animal = {
    createNew: function( bundle ) {
        var animal = {};
        var protected = bundle || {};
        protected.sound = "growl";
        protected.makeSound = function(){ 
            alert( protected.sound ) }
        return animal
    }
}
 
Cat = {
    createNew: function() {
        var protected = {};
        var cat = Animal.createNew( protected ); 
        protected.sound = "meow!";
        cat.meow = function(){ protected.makeSound(); };
        return cat;
    }
}
 
var cat = Cat.createNew();
cat.meow();

Note that the private properties are bound to an instance, you do not have to worry that they also refer to the same memory location. The following example demonstrates that you can safely use them as instance variables:

Cat = {
    createNew: function() {
        var cat = {};
        var sound = "meow";
        cat.makeSound= function(){
            alert( sound );
        }
        cat.setSound = function(a) {
            sound = a;
        }
        return cat;
    }
}
 
c1 = Cat.createNew();
c2 = Cat.createNew();
c1.makeSound();
c2.makeSound();
c1.setSound("Purrrrr"); 
//changes sound of cat 1 but not cat 2
 
c1.makeSound();
c2.makeSound();

Namespacing

A general concern with JavaScript objects is namespacing; while it's pretty easy to store objects in an organized way, few people actually use this feature:

if (!window._PACKAGES) window._PACKAGES = {};
if (!window._PACKAGES.AnimalPack)
 window._PACKAGES.AnimalPack = {};
if (!window._PACKAGES.AnimalPack.Cat) 
 window._PACKAGES.AnimalPack.Cat = { "myCat" };

The code above is somewhat verbose and clunky, so it's a good idea to create some tools to facilitate quality namespacing, here is an example:

window._PACKAGES = {
    _SYSTEM: {
        defPackage: function( pkg ) {
            var pkgParts = pkg.split(".");
            var path = window["_PACKAGES"];
            for(var i in pkgParts) {
                if (!path[pkgParts[i]]){
                    path[pkgParts[i]]={};
                }
                path = path[pkgParts[i]];
            }
            return path;
        }
    }
};

First create a reference to the 'defPackage' function, this will save you a lot of typing:

var pkg = window._PACKAGES._SYSTEM.defPackage;

Now you can easily define new classes in a neat package structure:

var Car = pkg("MyToyObjects.Car");
Car.createNew = etc ...

Augmentation and Prototype

In JavaScript you can extend objects on the fly, no need for complex class modifications, just add new public members whenever you like:

cat = Cat.createNew();
cat.bark = function(){ alert("whoof!"); }

A somewhat fancier solution is to use a blender; a blender is an object that adds extra functionality to an instance:

var pkg = window._PACKAGES._SYSTEM.defPackage;
var dog = Dog.createNew();
var cat = Cat.createNew();
var dogcat = pkg("_SYSTEM.blend")( cat, dog );
dogcat.bark();
dogcat.meow();

Here is my Object Blender:

blend: function( obj1, obj2 ) {
    for(var i in obj2){ obj1[i]=obj2[i] }    
    return obj1;
}

Note that this is not the same as multiple inheritance, but if you move this code to a constructor function you can assemble a new instance based on multiple builders, which comes quite close to multiple inheritance. Even better, this process allows you to avoid the infamous diamond of death problems that haunts languages offering multiple inheritance (http://en.wikipedia.org/wiki/Diamond_problem).

Prototyping is another way to do inheritance. It's uses less memory and it's faster then augmenting. I use prototypical inheritance for parts of my application that require more speed, however the whole prototypical syntax is clunky at best, and my eyes prefer to look at the previously described patterns, so in pratice I just use the best of both worlds.

To use the prototype notation you have to use the new-keyword, so instead of just using a plain object literal you need to create an instance of a function, here is an example (some browsers allow you to use the __proto__ property but that is not a standard!):

Cat = {
    CatFunc: function(){},
    createNew: function() {
        var cat = new this.CatFunc();
        return cat;
    }
}
 
c1 = Cat.createNew();
c2 = Cat.createNew();
Cat.CatFunc.prototype.eat = function(){ 
    console.log("eating") };
c1.eat();
c2.eat();

Using prototype you can also alter native objects like 'String' and 'Array', but you should be careful as other libraries don't expect these objects to change.

Navigate back to homepage.

Try it yourself!

Your personal Javascript notepad for this workshop!



 
copyright © Gabor de Mooij