Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
182 lines (167 sloc) 5.79 KB

Class.js – Javascript Inheritance

The Javascript’s language expressive nature is both its blessing and curse. It’s dynamic and functional roots allows it to do a lot with very little code. Its biggest short-comming is the lack of Object Orientated Programming built into the language.

Having spent years developing software I believe OOP is the best way to create re-usable and maintainable software. Not all the time (and you should favour composition over inheritance) but if your writing code that is going to be re-used (I term this ‘library code’ as opposed to ‘end user code’) it’s probably best done with OOP.

Javascripts flexible prototypal strengths really show itself when it allows you to build Object Orientated concepts right on top of the language. It’s actually too flexible in fact, as there are too many solutions each with their own strengths and weaknesses.

As defining and using objects is something I intend to do a lot, I wanted something that is both intuitive and terse. My cardinal rule of ‘good code’ is one that lets you express your intent intuitively with the least amount of noise as possible. As this is not something I found in the previous OOP solutions, I’m introducing Yet another Javascript Object Orientated Framework Class.js:

function Class() { }
Class.typeMap = {};
Class.$type = "AjaxStack.Class";
Class.getType = function() { return this.$type; }
Class.getTypeClassName = function()
{
    var parts = this.$type.split('.');
    return parts[parts.length - 1];
}
Class.prototype.getType = function()
{
    return this.constructor.$type;
}
Class.prototype.getTypeClassName = function()
{
    return this.constructor.getTypeClassName();
}
Class.mapAdd = function(array, key)
{
    array.push(key);
    array[key] = true;
    return array;
}
Class.registerType = function(type, ctor)
{
    if (!type) return;
    Class.typeMap[type] = ctor;
    var nsTypes = type.split('.'), ns = window, nsType;
    for (var i = 0; i < nsTypes.length; i++)
    {
        var nsType = nsTypes[i];
        if (!ns[nsType]) ns[nsType] = i < nsTypes.length - 1 ? {} : ctor;
        ns = ns[nsType];
    }
}
Class.prototype.getBaseTypes = function(types)
{
    var base = this, types = types || [];
    do {
        base = base.constructor.$base;
        if (base) Class.mapAdd(types, base.getType());
    } while (base);
    return types;
}
Class.prototype.getBaseTypesAndSelf = function()
{
    return this.getBaseTypes(Class.mapAdd([], this.getType()));
}
Class.prototype.isTypeOf = function(typeName)
{
    return this.getBaseTypesAndSelf()[typeName] ? true : false;
}
Class.getConstructor = function(typeName)
{
    return Class.typeMap[typeName];
}
Class.create = function(typeName, ctorArgs)
{
    var ctor = Class.typeMap[typeName];
    function F() {
        ctor.apply(this, ctorArgs);
    }
    F.prototype = ctor.prototype;
    return new F();
}
Class.inherit = function(subClass, baseClass)
{
    function F() { }
    F.prototype = baseClass.prototype;
    subClass.$base = baseClass.prototype;
    subClass.$baseConstructor = baseClass;
    subClass.prototype = new F();
    subClass.prototype.constructor = subClass;
}
Function.prototype.extend = function(a, options, members)
{
    Class.inherit(this, a);
    options = options || {};
    Class.registerType(options.type, this);
    this.$type = options.type || Class.$type;
    this.getType = Class.getType;

    if (members)
    {
        if (typeof (members) === 'function')
            members(this.prototype);
        else
            for (var a in members) this.prototype[a] = members[a];
    }
    return this.prototype;
};

Thats It! Its inspired by KevLinDev‘s previous efforts which like all good Javascript code is itself based on Douglas Crockfords prototypal inheritance. Let’s see some examples:

//Define your classes with a normal function definition
function Animal(firstName, lastName)
{
    //call the 'Class' contructor
    Animal.$baseConstructor.call(this);

    //Add any instance variables or other contructor logic here.
    this.firstName = firstName;
    this.lastName = lastName;
}
//Define what class you want to extend. Register the type name of this class
Animal.extend(Class, { type: 'Animal' },
    function(prototype)
    {
        prototype.hello = function(text)
        {
            return "hellooo: " + text;
        }
        prototype.getFullName = function()
        {
            return this.firstName + " " + this.lastName;
        }
    }
);

//Define the 'Cat' class
function Cat(catType, firstName, lastName)
{
    //Call the 'Animal' constructor.
    Cat.$baseConstructor.call(this, firstName, lastName);

    this.catType = catType;
}
//Extend Animal, and Register the 'Cat' type.
Cat.extend(Animal, { type: 'Cat' }, {
    //Another way to define your object's methods.
    hello: function(text)
    {
        return "meaoow: " + text;
    },
    getFullName: function()
    {
        //Call the base 'Animal' getFullName method.
        return this.catType + ": " + Cat.$base.getFullName.call(this);
    }
});

//create some animals and start playing with JS Inheritance!
var animal = new Animal("Mr", "Animale");
var cat = new Cat("ginger", "kitty", "kat");

And the all important results:

Code Result
animal.hello(1) hellooo: 1
animal.hello(2) hellooo: 2
cat.hello(3) meaoow: 3
animal.getFullName() Mr Animale
cat.getFullName() ginger: kitty kat
Animal.getType() Animal
Cat.getType() Cat
animal.getType() Animal
cat.getType() Cat
animal.getBaseTypes() Class
cat.getBaseTypes() Animal,Class
animal.getBaseTypesAndSelf() Animal,Class
cat.getBaseTypesAndSelf() Cat,Animal,Class
Class.createNew(“Cat”, ["tab","fat","cat"]).getFullName() tab: fat cat
animal.isTypeOf(Cat.getType()) false
animal.isTypeOf(Animal.getType()) true
cat.isTypeOf(Animal.getType()) true