Hallo
Das heißt, mit
var obj = { }
erzeugst du eine Instanz vonObject.prototype
[…]Das hast du unglücklich formuliert […]
Das habe ich unpräzise formuliert. Eigentlich sogar falsch. Aber nicht „unglücklich“! ;-)
Instanzen werden in JavaScript von Konstruktor-Funktionen gebildet.
So ist es. Aber bloß auf den Konstruktor zu verweisen würde der Sache auch nicht wirklich gerecht, oder?
Nehmen wir also einmal an, wir würden eines Morgens aus unruhigen Träumen erwachen und die folgende Funktion notieren…
var Kaefer = function (name) {
this.name = name;
this.typ = 'Ungeziefer';
this.farbe = 'braun';
};
…und diese sogleich als Konstruktor aufrufen:
var gregor = new Kaefer('Gregor');
Dann wäre es absolut nachvollziehbar zu sagen, dass gregor
ein Kaefer
ist und es sich hierbei also um eine Instanz des Konstruktors handelt:
var constructor = gregor.constructor; // function Kaefer()
oder
var check = gregor instanceof Kaefer; // true
Indem wir Kaefer
als Konstruktor aufrufen, erzeugen wir ein neues leeres Object
, welches wir innerhalb der Konstruktorfunktion über this
referenzieren können. Das heißt, wenn wir direkt im Konstruktor Eigenschaften und Methoden an die Variable this
knüpfen, dann sind dies, sofern wir die Funktion auch tatsächlich als Konstruktor aufrufen, Eigenschaften und Methoden des erzeugten Objektes selbst:
var ungeziefer = gregor.hasOwnProperty('typ'); // true
Würden wir hingegen das Schlüsselwort new
bei unserem Funktionsaufruf vergessen, dann hätten wir unter Anderem ein paar neue globale Variablen produziert…
Davon aber einmal abgesehen, hätten wir hier in diesem Fall auch gleich folgendes schreiben können:
var kaefer = function (name) {
return {
name : name,
typ : 'Ungeziefer',
farbe : 'braun'
};
};
var gregor = kaefer('Gregor');
var ungeziefer = gregor.hasOwnPrototype('typ'); // true
Wie dem auch sei, bezogen auf unseren Kaefer
-Konstruktor besteht soweit jedenfalls kein Zweifel an dessen Rolle als "Identitätsstifter" hinsichtlich der von ihm erzeugten Objekte.
Aber als Funktionsobjekt besitzt unser Konstruktor ja auch noch eine Eigenschaft namens prototype
, bei der es sich ebenso um ein (beinahe) leeres Object
handelt, welchem wir ebenfalls Eigenschaften und Methoden zuweisen können:
var Kaefer = function (name) {
this.name = name;
this.typ = 'Ungeziefer';
this.farbe = 'braun';
};
Kaefer.prototype.beruf = false;
Kaefer.prototype.gesundheit = 0;
Kaefer.prototype.bewerfen = function ( ) {
this.gesundheit -= Math.ceil(Math.random( ) * 10);
};
Wenn wir nun also Kaefer
als Konstruktor aufrufen, dann verfügt gregor
nicht nur über die direkt in der Funktion deklarierten Eigenschaften, sondern ebenso über die Eigenschaften und Methoden, welche von uns in Kaefer.prototype
hinterlegt wurden:
var gregor = new Kaefer('Gregor');
gregor.bewerfen( );
gregor.bewerfen( );
var status = gregor.gesundheit; // z.B. -7 :-(
Zwischen den direkt im Konstruktor angelegten Objekteigenschaften und denjenigen, welche wir im Prototypobjekt des Konstruktors spezifiziert haben, besteht allerdings ein Unterschied:
var farbe = gregor.hasOwnProperty('farbe'); // true
var beruf = gregor.hasOwnProperty('beruf'); // false
Bei den Eigenschaften, welche wir für das Prototypobjekt der Konstruktorfunktion angelegt hatten, handelt es sich also nicht um eigene Eigenschaften unseres Instanzobjektes, sondern um geerbte.
Jedes Objekt in JavaScript hat eine interne Eigenschaft [[Prototype]]
, welche standardmäßig auf das prototype
-Objekt seines Konstruktors verweist, und dem jeweiligen Objekt so dessen Eigenschaften und Methoden zur Verfügung stellt:
var prototyp = Object.getPrototypeOf(gregor);
Oder auch…
var prototyp = gregor.__proto__;
…wobei ich mir nicht sicher bin, ob letztere Schreibweise zum Zugriff auf die interne [[Prototype]]
Eigenschaft nun überholt ist oder nicht, aber die Einordnung in der Spezifikation unter legacy features spricht dafür, am besten grundsätzlich die erste Variante zu verwenden. ;-)
Jedenfalls hat auch der Prototyp eines Objektes selbst wiederum einen Prototyp, so dass eine Prototypenkette entsteht, die bei Object.prototype
und schließlich bei null
endet.
Als plain object ist der Prototyp von Kaefer.prototype
, in dem wir zusätzliche Eigenschaften hinterlegt haben, Object.prototype
, aber das kann man natürlich auch ändern, weshalb wir zunächst ein neues Objekt erstellen…
var properties = {
nachname : 'Samsa',
"familie" : ['Vater', 'Mutter', 'Grete'],
beruf : 'Handelsreisender',
hobbys : ['Zeitunglesen', 'Laubsägearbeiten', 'aus dem Fenster gucken'],
typ : 'Mensch'
};
…und welches wir dann als Prototypen von Kaefer.prototype
festlegen:
Object.setPrototypeOf(Kaefer.prototype, properties);
// Oder, wenn auch weniger empfehlenswert:
Kaefer.prototype.__proto__ = properties;
Nachdem wir dies also vollbracht haben, verfügt gregor
nun über eine ganze Reihe an Eigenschaften, von denen lediglich name
, typ
und farbe
eigene Eigenschaften sind, welche in der Konstruktorfunktion spezifiziert wurden, während der gesamte Rest über die Prototypenkette geerbt wurde.
Dabei soll allerdings nicht unerwähnt bleiben, dass "nachträgliche" Eingriffe in die prototype chain teure Operationen sind, die hier nur aus dramaturgischen Gründen vorgeführt wurden: In der Regel sollte die Kette unter Verwendung von Object.create( )
von unten aufgebaut werden, indem man der genannten Methode ein geeignetes Prototypobjekt als Argument übergibt, welches dann dem zurückgegebenen neuen Objekt als Wert der [[Prototype]]
Eigenschaft dient. Aber das nur nebenbei bemerkt.
Jedenfalls sollten wir einen Blick auf ein paar Eigenschaften von gregor
werfen:
var beruf = gregor.beruf; // false
Warum false
und nicht 'Handelsreisender'
, wie wir es gerade bestimmt haben?
Weil – ähnlich dem Scope von Variablen – nur dann in einem assoziierten Prototypobjekt nach einer entsprechenden Eigenschaft gesucht wird, wenn unter dem selben Bezeichner keine eigene Eigenschaft vorhanden ist.
Das heißt, da die Eigenschaft beruf
sowohl in Kaefer.prototype
als auch in dessen Prototypobjekt properties
hinterlegt ist, wird aus Sicht von gregor
auf die nächste Eigenschaft in der Kette, also die in Kaefer.prototype
bestimmte Eigenschaft beruf
mit dem Wert false
zugegriffen.
Wenn wir diese Eigenschaft löschen…
delete Kaefer.prototype.beruf;
…wird beim nächsten Prototypen in der Kette nachgefragt und voilà…
var beruf = gregor.beruf; // Handelsreisender
Und selbstverständlich handelt es sich bei gregor
nicht um 'Ungeziefer'
:
delete gregor.typ;
var check = gregor.typ; // 'Mensch' ;-)
Tja, und wenn wir uns nun anschauen, was gregor
an eigenen Eigenschaften geblieben ist, dann ist das nicht besonders viel, im Vergleich zu dem, was ihn sonst noch so ausmacht…
var names = Object.getOwnPropertyNames(gregor); // ['name', 'farbe']
…aber dennoch gilt nach wie vor:
console.log(gregor instanceof Kaefer); // true :-/
Also, was ist die Moral von der Geschichte?
Es war kein Traum! Obwohl fast alle Eigenschaften von gregor
aus der Prototypenkette geerbt wurden, ist er nach wie vor ein Kaefer
, also eine Instanz seines Konstruktors…
Aber beschreibt das die Herkunft und Identität unseres Objektes wirklich zufriedenstellend?
Wenn ich ein Array erzeuge, var arr = [ ]
, ist dann wirklich der Konstruktor Array( )
von Interesse oder nicht doch eher Array.prototype
, die Quelle der Methoden, die ich auf meinem Array anweden will?
Vielleicht ist der Begriff „Instanz“ im Zusammenhang mit der prototypischen Vererbung in JavaScript ganz einfach keine all zu treffende Bezeichnung. ;-)
Wie auch immer…
Gruß,
Orlok