Definition eines Konstruktors
Es ist aufwendig und fehlerträchtig, den Protoyp und die Felder zu jedem neuen Objekt hinzuzufügen, wie wir dies eben bei den Zählerobjekten gemacht haben.
Daher soll das Anlegen eines neuen Objektes der Objekttyps Zähler an einer Stelle zentralisiert werden.
Hierfür kommt zunächst einmal eine Funktion in Frage. Man spricht hier auch von einer Fabrikationsfunktion (factory function), weil sie Objekte fabriziert.
Es ist jedoch gebräuchlich, dafür einen Konstruktor zu verwenden, so daß wir uns nun erst einmal mit Konstruktoren beschäftigen.
Ein Konstruktor ist ebenfalls eine Funktion. Man nennt eine Funktion „Konstruktor“ wenn sie hinter »new« verwendet wird und dafür geschrieben wurde.
Bei Verwendung von new erzeugt ein Konstruktor eine neue Liste, welche innerhalb des Konstruktors »this« genannt wird.
function F(){ "use strict"; this.alpha = "Ada"; }
new F().alpha // "Ada" //
new F().constructor.name // "F" //
Wenn ein Objekt mit einem Konstruktor erzeugt wurde, so ist sein Prototyp normalerweise eine Prototyp-Variable des Konstruktors.
Object.getPrototypeOf( new F() )== F.prototype // true //
Die Prototyp-Variable des Konstruktors ist aber nicht mit dem Prototyp des Konstruktors gleichzusetzen!
Object.getPrototypeOf( F )== F.prototype // false //
Als Objekttyp eines Objektes kann man sich im allgemeinen den Namen des Konstruktors vorstellen. Aber nicht alle Objekte haben einen Konstruktor (wir hatten weiter oben Objekte ja ohne Konstruktor erzeugt).
Wir kennen schon einige vordefinierte Standardkonstruktoren von JavaScript, wie Number und String.
new String( "abc" ).constructor.name // "String" //
Die prototype-Variable des Konstruktors enthält eine constructor-Variable, welche normalerweise wieder der Konstruktor mit dieser prototyp-Variablen ist.
new String( "abc" ).constructor.prototype.constructor.name // "String" //
new String( "abc" ).constructor.prototype.constructor.prototype.constructor.name // "String" //
Man landet also immer wieder bei String! Deswegen kann man durch eine Abfolge von .constructor.prototype nicht die Prototyp-Kette hochlaufen. Dies geht nur mit Object.getPrototypeOf, welche den Prototyp eines Objekts ergibt (nicht den Inhalt seiner Prototyp-Variablen).
object.constructor = object.constructor.prototype.constructor
Konstruktoren schreibt man oft mit großem Anfangsbuchstaben (String, Number, F oben).
Definition von Feldern und Methoden mit Hilfe von Konstruktoren
Variablen eines Objekts nennt man auch dessen Felder.
Ein verbreitete und akzeptable Möglichkeit zum Anlegen von Objekten besteht darin, Felder, die in jedem Objekt als unabhängige Variable vorkommen sollen, im Konstruktor zu this hinzuzufügen, und Methoden, die für alle Objekte dieses Konstruktors gleich sein sollen, zum Prototyp des Konstruktors hinzuzufügen.
function Counter()
{ "use strict"; this.value = 0;
if( !Counter.prototype.inc )
Counter.prototype.inc =
function(){ return this.value++; }} // undefined //let a = new Counter(); // undefined //
let b = new Counter(); // undefined //
a.inc() // 0 //
a.inc() // 1 //
a.inc() // 2 //b.inc() // 1 //
b.inc() // 2 //
b.inc() // 3 //
Der Prototyp eines mit einem solchen Konstruktor erzeugten Objekts ist das Prototype-Feld des Konstruktors, und der Prototyp dieses Prototyps ist Object.prototyp, danach folgt nur noch null.
Object.getPrototypeOf( a )=== Counter.prototype // true //
Object.getPrototypeOf( Object.getPrototypeOf( a ))=== Object.prototype // true //
Object.getPrototypeOf( Object.getPrototypeOf( Object.getPrototypeOf( a )))=== null // true //
Konstruktorparameter
Wie eine Funktion kann ein Konstruktor auch Parameter haben.
function Counter( n )
{ "use strict"; this.value = n;
if( !Counter.prototype.inc )
Counter.prototype.inc =
function(){ return this.value++; }} // undefined //let a = new Counter( 10 ); // undefined //
let b = new Counter( 20 ); // undefined //
a.inc() // 10 //
a.inc() // 11 //
a.inc() // 12 //b.inc() // 20 //
b.inc() // 21 //
b.inc() // 22 //
Vergleich zwischen Objektliterale und Alternativen
Alternativen:
- let a = { x: 1 };
- let a = new Object();
a.x = 1; - let a = Object.create( Object.prototype )
Object.defineProperty( a, "x", { value: 1, configurable: true, enumerable: true, writable: true });
Im letzten Fall hat a aber keine constructor-Variable
Das folgende Beispiel zeigt eine einfach Möglichkeit zur Definition eines Zählers, wenn keine weiteren Zähler benötigt werden:
let c = { value: 0, inc: function(){ return this.value++; }} // undefined //
c.inc() // 0 //
c.inc() // 1 //
c.inc() // 2 //
Überschreiben des Prototyps
Man sollte aber nicht unbedingt einfach das Prototypfeld des Konstruktors überschreiben, weil dabei die Prototypkette des Konstruktors verloren gehen kann.
function Counter()
{ "use strict"; this.value = 0; } // undefined //Counter.prototype =
{ inc: function(){ return this.value++; }} // Object { inc: Counter.prototype.inc() } //let d = new Counter(); // undefined //
In den folgenden drei Zeilen ist aber noch alles in Ordnung. Das Problem zeigt sich erst in anderen Fällen.
Object.getPrototypeOf( d )=== Counter.prototype // true //
Object.getPrototypeOf( Object.getPrototypeOf( a ))=== Object.prototype // true //
Object.getPrototypeOf( Object.getPrototypeOf( Object.getPrototypeOf( a )))=== null // true //
Klassendeklarationen
In neueren JavaScript-Versionen wird es auch Klassendeklarationen geben, aber diese sind letztendlich nur eine andere Schreibweise für die hier schon vorgestellten Verfahren zum Anlegen von Objekten.