Javascript OO Without Constructors

Javascript's object model is not very well known. I've written about them a while back. One reason it is not well known is because it is the only widely used language that uses prototype inheritance. But I think another reason it is not well known is because it is complicated and confusing to explain. Why is it so complicated and confusing? Because Javascript tries to hide its true nature with a classical OOP facade - ending up a Jekyll and Hyde situation.

I believe that the inaccessibility of its object model is at least one of the reasons there's so much interest around compile-to-js languages like CoffeeScript, Dart, and TypeScript.

Javascript old-timers and die-hards believe that Javascript has a better object model and lament the fact that this may forever be forgotten. Even long time Javascript guru Nicholas Zakas welcomes the addition of the the new class syntax - which is simply syntactical sugar for the prototypal style - to version 6 of the language. Alas, classical OOP has won.

A Thought Experiment

However, just for fun, here's a thought experiment: let's suppose we turn back the clock on history, and that classical OOP hadn't gained wide adoption as it did, and instead, a prototype-based inheritance model received wide acceptance instead. What would have happened? What kind of design patterns would we have ended up with?

Here's another thought: what if Javascript did not have constructors, or the "new" keyword? How would things have turned out? Let's reinvent the wheel :)

First things first, to create a new object in Javascript, you can just use an object literal

var felix = {
    name: 'Felix'
    , greet: function(){
        console.log('Hello, I am ' + this.name + '.')
    }
}

Now what if we want to generalize the greet function to a common place - a pull-up, if you will - so that we can create multiple objects which share the greet function? We have a few options here, let's start with the mixin.

Mixin (Augmentation)

In Javascript, mixins are embarrassingly simple. All you have to do is to copy the properties from the "mixin object" to the object you want to mix it into. We will use a function called "augment" to do this

var Dude = {
    greet: function(){
        console.log('Hello, I am ' + this.name + '.')
    }
}
var felix = { name: 'Felix' }
augment(felix, Dude)

So augment applies the object Dude as a mixin into felix. In most libraries, the augment function is called "extend". I don't like extend because some languages use extend to mean inherit, and as a result it always confuses me. I like "augment" because it's not extend and because the syntax augment(felix, Dude) indicates very clearly that you are augmenting felix with what is in Dude.

As you might have guessed, the code for augment is simply

function augment(obj, properties){
    for (var key in properties){
        obj[key] = properties[key]
    }
}

Cloning

An alternative to using a mixin, is first to clone Dude, and then set the name property on the cloned version.

var Dude = {
    greet: function(){
        console.log('Hello, I am ' + this.name + '.')
    }
}
var felix = clone(Dude)
felix.name = 'Felix'

The only difference here is the order in which the properties are applied. You might use this technique if you plan to override some methods in the cloned object.

var felix = clone(Dude)
felix.name = 'Felix'
felix.greet = function(){
    console.log('Yo dawg!')
}

Calling back to super's method is simple - just use the apply method

felix.greet = function(){
    Dude.greet.apply(this)
    this.greetingCount++
}

This is nicer than the prototypal style because you don't have to go through the .prototype property of a constructor - we are not using any constructors.

The implementation of clone is

function clone(obj){
    var retval = {}
    augment(retval, obj)
    return retval
}

Inheritance

Finally we have inheritance. In my opinion, inheritance is overrated, but it does have the advantage over augmentation in that you can share properties between the "instance objects". Let's make an inherit function which takes an object as input and returns a new object that inherits the original object.

var felix = inherit(Dude)
felix.name = 'Felix'

With inherit, you can have multiple objects inherit the same one, and the children will inherit the parent's properties in realtime.

var garfield = inherit(Dude)
Dude.walk = function(){
    console.log('Step, step')
}
garfield.walk() // prints "Step, step"
felix.walk()    // also prints "Step, step"

The inherit function is where the prototype inheritance magic happens

function inherit(proto){
    if (Object.create){
        // uses the ES5 Object.create method
        return Object.create(proto)
    }else if({}.__proto__){
        // uses the non-standard __proto__ property
        var ret = {}
        ret.__proto__ = proto
        return ret
    }else{
        // fallback to using a constructor if
        // neither of the above are available
        var f = function(){}
        f.prototype = proto
        return new f()
    }
}

It looks a little crazy because we are using feature detection to determine which of 3 ways to use.

Initialization

But what about constructors (a.k.a initializer method) ? How do you share initialization code between instances of objects? In the cases where all you are doing is setting a couple of properties, an initializer method is not necessary, as seen in the above examples. But if you have more code initialization code, you might just use a convention such as: a method called "initialize" is an initializer. Let's say you have an initialize method defined in Dude

var Dude = {
    initialize: function(){
        this.greetingCount = 0
    }
    , greet: function(){
        console.log('Hello, I am ' + this.name + '.')
        this.greetingCount++
    }
}

Then, to initialize an object you would do

var felix = clone(Dude)
felix.name = 'Felix'
felix.initialize()

or alternatively

var felix = { name: 'Felix' }
felix.name = 'Felix'
augment(felix, Dude)
felix.initialize()

or alternitively still

var felix = inherit(Dude)
felix.name = 'Felix'
felix.initialize()

Closing Thoughts

I claim that with the three functions defined above - augment, clone, and inherit - you can do anything you might want to do with objects - without using any constructors or the "new" keyword. I also think that the semantics embodied by these functions are simpler and closer to Javascript's underlying object system.

blog comments powered by Disqus