Overerving in JavaScript

Sinds de ontwikkeling van JavaScript wordt het al onderschat. Eerst was de naam LiveScript, maar dat was niet verwarrend genoeg, dus is LiveScript hernoemd naar JavaScript. En dat terwijl het niet van Java afstamt, het niet op Java is gebaseerd en niet op Java lijkt, althans niet meer dan andere programmeertalen.

In JavaScript ontbreken classes en voor programmeurs die Java of C++ gewend zijn is dat wennen, maar zaken als inheritance zijn toch mogelijk in JavaScript door middel van prototypes.

Voorbeelden

Voor de volgende voorbeelden worden een paar snippets gebruikt. Deze zijn afkomstig van Douglas Crockford.

Normale overerving

function Animal(name)
{
    this.setName(name);
}

Animal.method('setName', function(value){
    this.name = value;
});

Animal.method('getName', function(){
    return this.name;
});

Animal.method('toString', function(){
    return 'Animal(' + this.getName() + ')';
});

Eerst maken we een klasse Animal, de parent classe. Daarna voegen we daar methoden aan toe. De functie ‘method’ komt uit de snippets en zorgt ervoor dat de methoden aan het prototype worden toegevoegd.

We kunnen nu dus dit doen:

animal = new Animal("Klaartje");
console.log(animal.toString());

Er staat nu zoals verwacht “Animal(Klaartje);” in de console.

Nu maken we een nieuwe classe Koe en vervangen de methode toString:

function Koe(name)
{
    this.setName(name);
}

Koe.inherits(Animal);

Koe.method('toString', function(){
    return 'Koe(' + this.getName() + ')';
});

We kunnen nu dus dit doen:

koe = new Koe("Klaartje");
console.log(koe.toString());

Er staat nu “Koe(Klaartje);” in de console. De functies getName en setName zijn namelijk over geerfd.

Het is dus mogelijk met javascript zaken als overerving te regelen, ook al zijn er eigenlijk geen classes.

Uitgebreide overerving

Met de “swiss”-methode is het mogelijk om een classe op te bouwen uit methodes van meerdere verschillende classes.

function Viervoeter(name, length)
{
    this.setName(name);
    this.setLength(length);
}

Viervoeter('setLength', function(value){
    this.length = value;
});

Koe.swiss(Viervoeter, "setLength");

We kunnen nu dit schrijven:

dier = new Koe("Henk");
dier.setLength("2 meter");
dier.setName("Mike");

Het is nu dus mogelijk methoden uit Koe, uit Viervoeter en uit Animal te gebruiken.

Super

Zoals Java super heeft, zit er in de snippets voor JavaScript de functie “uber” ingebouwd. Met behulp van deze functie kunnen we de constructor van Viervoeter herschrijven:

function Viervoeter(name, length)
{
    uber(name);
    this.setLength(length);
}

Viervoeter.inherits(Animal);

Conclusie

Door enkele kleine functies te schrijven (method, inherits, swiss) is er in JavaScript binnen no-time een goede vervanger voor classes. Het mooie van JavaScript is dan ook dat het zo flexibel is.

Snippets

De methode uit de eerste snippet zorgt ervoor dat er een methode wordt toegevoegd aan het prototype van de class. Omdat de methode niets hoeft terug te geven returnen we this zodat het mogelijk is de functies aan een te rijgen.

Function.prototype.method = function (name, func)
{
    this.prototype[name] = func;
    return this;
};

De tweede methode is iets spannender. Deze krijgt één parameter mee, namelijk de classe die inherited/extended moet worden. De methode stelt het prototype van de functie in op een instancie van de parent en voegt ook nog één methode “uber” toe. Deze “uber”-methode is in feite de “super” uit de Java wereld.

Function.method('inherits', function (parent) {
    var d = {}, p = (this.prototype = new parent());
    this.method('uber', function uber(name) {
        if (!(name in d)) {
            d[name] = 0;
        }
        var f, r, t = d[name], v = parent.prototype;
        if (t) {
            while (t) {
                v = v.constructor.prototype;
                t -= 1;
            }
            f = v[name];
        } else {
            f = p[name];
            if (f == this[name]) {
                f = v[name];
            }
        }
        d[name] += 1;
        r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
        d[name] -= 1;
        return r;
    });
    return this;
});

Wanneer je enkele maar niet alle methode van een classe wilt overerven kan dat ook met de methode “swiss”. Deze heeft één vaste parameter parent waarvan moet worden overgeerfd en alle overige gegeven parameters worden opgevat als functienamen. Deze worden vervolgens toegevoegd aan het prototype van de nieuwe classe.

Function.method('swiss', function (parent) {
    var a = arguments;
    for (var i = a.length; --i;) {
        this.prototype[a[i]] = parent.prototype[a[i]];
    }
    return this;
});

Bron: Douglas Crockford’s javascript.crockford.com