Javascript Object Prototype

Javascript Object Prototype

Alle Javascript-Objekte ((Date, Array, RegExp, Function, …)) erben Eigenschaften und Methoden von Object.prototype, das gilt sowohl für selbstgemachte Objekte als auch für die vorgefertigten Objekte. So erben z.B. Date-Objekte von Date.prototype, Arrays von Array.prototype und auch die DOM-Objekte, die Element, Event, Document und weitere Schnittstellen des Document Model Objects darstellen.

18-12-15 SITEMAP CSS HTML JS Basis JS Web Tutorial SVG

Prototyping

Wenn ein neues Objekt angelegt wird, hat es eine Referenz zu seinem Vorfahren oder Vorbild. Der Vorfahre ist der Prototyp – die Vorlage, von der alle Instanzen Eigenschaften und Methoden erben. Diesem Prototype können wir weitere Eigenschaften und Methoden hinzufügen.

Das ist eine Technik, die sowohl bei den eingebauten Objekten wie Date() als auch bei eigenen Objekten angewendet wird. Erweitern wir z.B. das Javascript Date-Objekt um die Methode subtractDays, um das Datum vor einer bestimmten Zahl von Tagen zu finden:

Date.prototype.subtractDays = function (d) {
	this.setTime (this.getTime () - (d*24*60*60*1000));
	return this;
}
let today = new Date ();
today.subtractDays (30);

Die nativen Prototypen von Arrays, Strings oder HTML-Elementen sollten nicht geändert werden (gilt als schlechte Programmierpraxis).

ES6 hat uns die __proto__ Eigenschaft beschert, die stets auf den Prototypen eines Elements verweist. Wozu, wenn wir doch schon eine prototype-Eigenschaft haben? __proto__ war von Anfang an umstritten und gilt heute als veraltet.

Prototyping HTML DOM Elemente

Wäre es nicht schön, wenn wir DOM-Elemente so einfach erweitern könnten? Z.B. um so häufig benutzte Methoden wie jQuery hide() und show() für natives reines Javascript zu nutzen?

Das würde eigentlich gut funktionieren.

Prototyping HTML-Elements
Element.prototype.show = function(){
	this.classList.remove("hide");
	this.classList.add("show");
};

Element.prototype.hide = function(){
	this.classList.remove("show");
	this.classList.add("hide");
};

document.querySelector("#elem").hide();

Klingt gut, funktioniert allem Anschein nach gut, das DOM-Scripting würde so schön objektorientiert, aber die manuellen Erweiterungen des DOM sind langsam.

Und was passiert, wenn es die Erweiterungen show und hide schon gäbe? Oder wenn sie morgen in die neue Javascript-Version aufgenommen werden? Oder wenn die Browser-Implementierung eine Erweiterung mit diesem Namen nutzen würde?

So, aber wie kriegt jQuery diese Erweiterung in den Griff? jQuery erzeugt – anders als viele heute vergessene Libraries – einen Wrapper um die Elemente und delegiert die Methoden hierhin. So kommt es nicht zu Kollisionen.

let myElementWrapper = function (foo) {
	this.container = document.createElement("myHTMLElement");
}

myElementWrapper.prototype.neueMethode = function () {
	
}

Prototyping eigene Objekte

Die einfachste Technik beim Anlegen eines neuen Objekts ist ein Object Literal.

let spaceship = {
	key1: value,
	key2: value
};

Wenn mehrere Kopien des Objekts gebraucht werden, muss redundanter Code geschrieben werden. Für solche Fälle bietet Javascript Konstruktor-Funktionen.

function Spaceship (name, build, age) {
    this.name = name;
    this.baujahr = build;
    this.age = age;
}

Mit einer Konstruktor-Funktion (der Name von Kontruktor-Funktionen beginnt üblicherweise mit einem Großbuchstaben) und new werden neue Objekte vom selben Prototypen angelegt.

let voyager    = new Spaceship ("Voyager", "2400", 7);
let enterprise = new Spaceship ("Enterprise", "2200", 5);

Neue Eigenschaft hinzufügen

voyager.captain = "Janeway";

Die Eigenschaft wird nur zu voyager hinzugefügt, nicht aber zu enterprise.

[Log] Spaceship {name: "Voyager", baujahr: "2400", age: 7, captain: "Janeway"} 
[Log] Spaceship {name: "Enterprise", baujahr: "2200", age: 5}

Dem Prototyp kann eine neue Eigenschaft nicht derart locker hinzugefügt werden wie einem Objekt, denn der Prototyp ist kein existierendes Objekt.

function Spaceship (name, build, age, captain) {
   this.name = name;
   this.baujahr = build;
   this.age = age;
   this.captain = captain;
}

Das ist ein einfaches Muster für das Erzeugen und die Wiederverwertung von Objekten. Die Vererbung erzeugt eine Kette, die zurückreicht bis zum grundlegenden object.prototype. Neue Eigenschaften müssen in die Prototyp-Funktion geschrieben werden.

newObject foo Spaceship foo

Hat das neue Objekt eine Eigenschaft foo, dann wird die Kette der Vorgänger nicht konsultiert. Wenn der Zugriff fehlschlägt, folgt Javascript der Kette der Vorgänger.

Änderungen im alten Objekt schlagen sofort auf die abgeleiteten Objekte durch, aber Änderungen in abgeleiteten Objekten berühren die Vorgänger nicht.

Methoden hinfügen

function Spaceship (name, build, age, captain) {
   this.name = name;
   this.baujahr = build;
   this.age = age;
   this.captain = captain;
   this.mission = function() {
      return "Raumschiff " + this.name + " Neue Welten entdecken";
    };
}

let voyager    = new Spaceship ("Voyager", "2400", 7, "Janeway");
let enterprise = new Spaceship ("Enterprise", "2200", 5, "Kirk");

console.log(voyager.mission());
Raumschiff Voyager Neue Welten entdecken

Eigenschaften und Methoden mittels prototype-Eigenschaft hinzufügen

Eigenschaften

Spaceship.prototype.crew = 400;

Methoden

Spaceship.prototype.mission = function() {
    return "Neue Welten erforschen";
};

Javascript Classes

Zwar sind Javascript Prototypes mit etwas Routine einleuchtend, aber der Script-Code wirkt schnell unorganisiert. Wer schon mal ein Script übernommen und versucht hat, den Script-Code aufzuschlüsseln (wer erbt denn jetzt was von wem?), kann ein Lied davon singen.

Mit ES6 hat Javascript eine neue Syntax – Javascript class – eingeführt, die sich enger an traditionelle klassenorientierte Programmiersprachen hält und die verschlungenen Familienverhältnisse deckelt.

Mit dem Schlüsselwort class wird eine Klasse als Template oder Vorlage angelegt. Das Schlüsselwort extends benennt eine andere Klasse, von der eine Klasse Eigenschaften und Methoden erbt. Das macht die Kette der Vererbung (chain of inheritance) öffentlich sichtbar. Damit das Scripting einfacher, gleichzeitig aber auch der Einblick und Durchblick bei einem vorhandenen und übernommenen Script. Für ältere Browser, die ES6 noch nicht unterstützen, können Javascript Klassen per Transpiler in den älteren Scriptcode übersetzt werden.