JavaScript Array Inheritance

JavaScript beherrscht eine Art Vererbung von Klassen auf Prototyp-Ebene. Die Technik läuft ja eigentlich in allen Browsern gut. Die Ausnahme ist wiedermal der IE7, den noch etliche Kunden gezwungen sind zu nutzen...

Für gewöhnlich leite ich Objekte im meinem JavaScript-Code in etwa so ab:

function FancyObject(){};
FancyObject.protoype = new FantasticParentObject();
FancyObject.protoype.awsomeFunction = function(){
	// some code...
};

var fancyInstance = new FancyObject();
fancyInstance.awsomeFunction();
...

Bisher waren auch alle Browser damit einverstanden und verrichteten ihren Dienst wie erwartet.

Doch es gibt eine kleine Macke im IE7, die meinen Kollegen und mir die Haare zu Berge stehen lassen hat. Die Ableitung von Arrays ist im IE7 nicht möglich... schade, dabei wäre die Erweiterung eines Arrays um eine neue Methode sooo ertragreich gewesen.

function FancyArray(){};
FancyArray.protoype = new Array();
FancyArray.protoype.fire = function(param){
	for(var i=0; i < this.length; i++){
		this[i](param);
	}
};

var fancyArray = new FancyArray();
fancyArray.push( function(){ alert("foo") } );
fancyArray.push( function(){ alert("bar") } );
fancyArray.fire();
...

Jeder andere Browser führt den Code so aus, dass Array abgeleitet wird zu FancyArray und dabei eine neuen Methode erhält, die fire() heißt. Diese soll alle ihre gesammelten Funktionen der Reihe nach aufrufen. Eigentlich ganz einfach.

IE7 Bug

Dummerweise gibt es im IE7 einen Bug, der offenbar zu einem Missmatch zwischen Arrayinhalt und Arraylänge führt. Während die Elemente (Funktionen) im Array mit fancyArray[0] erreichbar sind, ist die Länge length immer noch 0. Das macht nur der IE7 so, was für mich nur auf einen Bug hindeutet.

Lösungsversuch 1

Begegnen wir dem Fehler mal plump und verzichten auf die length Prüfung in der Methode fire(). Das kann dann Erfolg haben, wenn wir niemals mehr als den Array um das feuern von Methoden bitten. In allen anderen Fällen wird aufgrund der Falschaussage length == 0 der Code Kopfschmerzen bereiten (im IE7 wohlgemerkt, dessen Debuggingmöglichkeiten auch eher schlecht denn recht sind).

function FancyArray(){};
FancyArray.protoype = new Array();
FancyArray.protoype.fire = function(param){
	// Solange Elemente durchwühlen, solange es etwas zu greifen gibt, 
	// ohne die echte Länge zu kennen! Oh, ich komme mir so schmutzig vor!
	for(var i=0; this[i]; i++){
		this[i](param);
	}
};

var fancyArray = new FancyArray();
fancyArray.push( function(){ alert("foo") } );
fancyArray.push( function(){ alert("bar") } );
fancyArray.fire();
...

Lösungsversuch 2

Der kreative Ansatz: Ist man nicht auf ein echtes abgeleitetes Array angewiesen und muss nicht etwa so Krams machen wie array1 instanceOf Array, dann geht es auch wie folgt. Mann erzeugt ein FancyFireObject mit namentlich gleichen Funktionen des Arrays und leitet alles zu einem Array-Attribut im Innern des FancyFireObjects, z.B. mit dem Titel _list.

function FancyFireObject(){
	this._list = [];
};
FancyFireObject.protoype.push = function(param){
	this._list.push(param);
};
FancyFireObject.protoype.fire = function(param){
	for(var i=0; i < this._list.length; i++){
		this._list[i](param);
	}
};

var fancyArray = new FancyFireObject();
fancyArray.push( function(){ alert("foo") } );
fancyArray.push( function(){ alert("bar") } );
fancyArray.fire();
...

Fazit

Weder der eine noch der andere Hack macht wirklich Spass. Die maintainance leidet erheblich.

Somit ist ab sofort der alte IE7 der neue IE6... und der muss weg, so schnell es geht!