Javascript 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.
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);
Dem Objekt 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.
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 class
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.