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. Date-Objekte erben vom Date.prototype, Arrays vom Array.prototype und so auch die DOM-Objekte.

23-02-02 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 DOM-Elementen sollten nicht geändert werden (gilt als schlechte Programmierpraxis).

Konstruktor-Funktionen

Ein Prototyp ist ein besonderes Objekt, dem Methoden / Eigenschaften hinzugefügt wurden, die allen Instanzen zugute kommen. Der Weg zur seriellen Konstruktion von Objekten von einem Prototypen führt über die Konstruktor-Funktion.

Konstruktor-Funktionen sind nichts Neues. Ein Datum wird ebenfalls mit einer Konstruktor-Funktion angelegt. Das Schlüsselwort new initialisiert dabei eine Konstruktor-Funktion.

const jetzt = new Date ();

Die Konstruktor-Funktion wird üblicherweise mit einem großen Anfangsbuchstaben geschrieben.

function Book (titel, autor, preis, titelbild, col) {
	this.titel = titel;
	this.autor = autor;
	this.preis = preis;
	this.col = col;
	this.titelbild = titelbild;

	this.createImage = function () {
		const img = document.createElement ("img");
		img.src = this.titelbild;
		document.querySelector (this.col).append (img);
	}
  
  this.publish = function () {
    const p = document.createElement ("p");
    p.innerHTML = `${this.titel} <br> 
                   ${this.autor} <br> ${this.preis}`;
    document.querySelector (this.col).append (p);
  }
}

Neben den Eigenschaften hat das Objekt Book zwei Funktionen – also Methoden – : createImage () und publish ().

Instanzen anlegen

Konstruktor-Funktionen erzeugen beliebig viele Instanzen des Objekts. Das ist der Ablauf:

  • Jeder Aufruf von new erzeugt ein neues leeres Objekt.
  • Die Konstruktor-Funktion wird mit den Eigenschaften des Objekts als Argument aufgerufen.
  • Das Schlüsselwort this wird auf das neue leere Objekt gesetzt.
  • Die Konstruktor-Funktion gibt das neue Objekt zurück.
const Book1 = new Book ("Stolz und Vorurteil", "Jane Austen", 17.95, "images/austen.png", ".book1");
Book1.createImage ();
Book1.publish ();


const Book2 = new Book ("Alice im Wunderland", "Lewis Carroll", 15.75, "images/Alice.png", ".book2");
Book2.createImage ();
Book2.publish ();

Prototyping

Das Anlegen der Methoden innerhalb der Konstruktor-Funktion ist unflexibel und fest verdrahtet. Anstelle dessen kommt jetzt das Prototyping zum Zug.

function Book (titel, autor, preis, titelbild) {
	this.titel = titel;
	this.autor = autor;
	this.preis = preis;
	this.titelbild = titelbild;
}

Book.prototype.createImage = function () {
	const img = document.createElement ("img");
	img.src = this.titelbild;
	return img;
}

Book.prototype.publish = function () {
	const p = document.createElement ("p");
	p.innerHTML = `${this.titel} <br> ${this.autor} <br> € ${this.preis}`;
	return p;
}

Die Instanzierung unterscheidet sich nur leicht.

const Book1 = new Book ("Stolz und Vorurteil", "Jane Austen", 17.95, "austen.png");
document.querySelector (".Book1").append (Book1.createImage ());
document.querySelector (".Book1").append (Book1.publish ());

Dem Prototypen können jederzeit neue Eigenschaften und Methoden hinzugefügt werden, die sich an alle Instanzen automatisch vererben.

Hinter den Kulissen

Die constructor-Methode findet heraus, welche Konstruktor-Funktion das Objekt angelegt hat. Andererseits gibt instanceof Book (instanceof ist keine Methode, sondern ein Operator wie typof) true zurück, wenn wie wissen wollen, ob ein Objekt tatsächlich von einem bestimmten Konstruktor angelegt wurde.

console.log (Book1.constructor);
console.log (Book1 instanceof Book);

Primitive Datentypen wie String und Number haben keine Methoden – eigentlich. Aber im Hintergrund packt Javascript auch die primitiven Datentypen in ein Objekt. So deklariert const x = 12 eine Zahl, genauso gut konstruiert const x = new Number (12) eine Zahl.

const x = new Number (12);
console.log (12);

const y = 12;
console.log (y);

console.log ("y instanceof Number", x instanceof Number); // true 
console.log ("y instanceof Number", y instanceof Number); // false 

Erst das Einpacken der primitiven Datentypen (auch als »boxing« beschrieben) in ein Objekt verschafft uns die komfortablen Methoden für Strings.

Auch Arrays können mittels Konstruktor angelegt werden, aber da Arrays bereits Objekte sind, macht es keinen Unterschied, ob die literale Array-Deklaration angewendet wird oder der Konstruktor new Array.

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 () {
	
}

class – Klassen in JavaScript – Baupläne für Objekte

Zwar sind Javascript Prototypes mit etwas Routine einleuchtend, aber das Script 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 wird das Scripting einfacher, der Einblick und Durchblick bei einem vorhandenen und übernommenen Script fällt leichter.

class Buch {
	constructor (titel, seiten) {
		this.titel = titel;
		this.seiten = seiten;
	}
	
	logger () {
		console.log (this.titel, this.seiten);
	}
}

const buch = new Buch ("JavaScript lernen", 300);
buch.logger ();