A simple(r) approach for class-based OOP in JavaScript.

Like others before me, I found JavaScript’s prototype-based OOP support – while very powerful – quite cumbersome in some situations where I needed a certain structure or hierarchy of objects, situations in which one quickly comes to the conclusion that the best answer would be the concept of a Class.

However, while I am well aware that the web is filled with various implementations of classical (i.e. class-based) object orientation in JavaScript, I can not help the feeling that they are over-engineered and over-complicated without need, quite often attempting to provide more functionality than is actually necessary. In short, I don’t feel they’re “classical” at all.

This is why I started working on my own implementation of classical OOP  in JavaScript, which is shown below. Currently this is only a proposal, although I already started using it in one of my projects. Anyway, let’s look at it first then I’ll try and explain how it works.

The Code

/** Class factory. */
function Class(members) {

    // proxy constructor
    var Proxy = function() {
        for (var item in members) {
            this[item] = members[item];
        }
    };

    // proxy inheritance
    Proxy.prototype = (members.base || Class).prototype;

    // class constructor
    var Build = members.init || function() {};

    // class inheritance
    Build.prototype = new Proxy();
    Build.prototype.base = Proxy.prototype;
    Build.prototype.constructor = Build;

    // ready
    return Build;
}

That is all, and this little function can have a whole world of nifty consequences like deep inheritance or access to the super-class via this.base (and others). But here is what happens: the basic principle at work here is the Proxy object, which essentially, contains all the members of our new class (the ones that are passed to the Class factory function as the members parameter).

So, instead of copying the given members straight into the newly created class, we put them into another object (i.e. the Proxy) and make that the prototype of our new class. This means that every class will point (via the prototype property) to an internal Proxy object holding the actual class members. The trick is that this Proxy, in turn, points to the ancestor’s Proxy object (again via the .prototype property) and so on.

The code (and further updates) may be found here.

The Concept

Here is a visual depiction of the system:

This trick enables one very important feature: classic class inheritance via the prototype property. Class members are never duplicated and the only additional objects ever created are the Proxy objects (one per class). Here are some features:

The Simplicity

You can create a class very easily, using a constructor (i.e. init()) and this class can have instance members, class members and/or static members:

var Person = Class({

    // constructor
    init: function(fname, lname, age, job) {

        // class member
        this.first_name = fname;
        this.last_name = lname;

        // instance members
        this.age = age;
        this.job = job;
    },

    // class members
    first_name: '',
    last_name: '',
});

// static member
Person.nationality = 'Romanian';

// create instance
var man = new Person('George', 'Enescu', 33, 'Classical Composer');

The Deep Inheritance

Classes can have single, but deep, inheritance (i.e. inheritance chains) where each object has direct access to the super-class via the .base property:

var A = Class({
    hello: function() {
        console.log('Hello world!');
    }
});
var B = Class({ // inherit A
    base: A,
    aloha: function() {
        console.log('Aloha world!');
    }
});
var C = Class({ // inherit B
    base: B,
    aloha: function() { // override method from B
        console.log('Goodbye world!');
    }
});

var c = new C();
c.hello(); // 'Hello world!'
c.aloha(); // 'Goodbye world!'
c.base.aloha.call(c); // 'Aloha world!'

Note: the c.base.aloha() method was invoked via call() in order to apply it onto the c object; invoking it as c.base.aloha() would have applied it onto the c.base object.

The Prototypal Concepts

The regular functionality of the prototype property is kept intact, so that changes to A.prototype will implicitly propagate to everything derived from class A (all instances, all sub-classes, all instances of sub-classes etc.):

// extend class A
A.prototype.doStuff = function() {
    console.log('Am doing some extra stuff!');
}

// check any child
c.doStuff(); // 'Am doing some extra stuff!'

[Update] This also means that you may affect the prototype of any class form an inheritance chain and it will do what you expect (i.e. propagate changes down the chain to all the children below that class).

Also the instanceof operator behaves as expected:

console.log(c instanceof A); // true
console.log(c instanceof B); // true
console.log(c instanceof C); // true

The .prototype.constructor property is also properly configured, so if you want, you can create new instances starting from an initial object, by extracting the object’s constructor:

// identical to C.prototype.constructor
var Cons = Object.getPrototypeOf(c).constructor; 

// create new object
var c2 = new Cons();

The Efficiency

Inheritance is provided without creating a new instance of the base class for every prototype. This is unnatural anyway since it calls the base constructor without arguments, which may have unwanted side-effects:

NewClass.prototype = new BaseClass(); // the weird way of prototyping

Also, class members are never copied between classes and no duplicates are ever created, thus saving memory and ensuring high access speed for the class members.

In addition, the entire code is only a few lines long (no, I’m not going to count), so it has a certain elegance to it.

The Conclusion

Maybe this technique is neither new, nor very full featured, but honestly, if I want mixins, extensible objects and other such exotic features, then why would I bother with old-school OOP classes anyway; the JavaScript prototypal approach is capable of all that, and more!

I’m not saying that this is *the* way of doing classic OOP in JavaScript, but I certainly feel it is more in-line with what I perceive to be the JavaScript mindset: keep it simple, use what you have at your disposal and don’t kill yourself trying to emulate other languages!

That being said I also think this code may be improved, so pitch in a comment or two if you like :D…

P.S.: For example, the Build() constructor may be easily modified to enforce the concept of abstract classes, effectively refusing to create the instance (i.e. via throw) if it detects that any of its members (inherited or not) is purely virtual (i.e. set to null or undefined). So a sub-class must either implement all the purely virtual members it inherits, or it will simply become abstract as well (I actually use this trick in production).

Comments are closed.