Verwendung von Prototypen in JavaScript [] (Verwendung von Prototypen in JavaScript), Lektion, Seite 723191
https://www.purl.org/stefan_ram/pub/call_javascript (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram

Verwendung von Prototypen in JavaScript

Verwendung vordefinierter Prototypen

Wenn zwei Objekte die gleiche Methode haben sollen, so kann sie in einen Prototyp ausgelagert werden. (Wenn zwei Prototypen die gleiche Methode haben sollen, kann diese wiederum in einen Prototyp der jener Prototypen ausgelagert werden.)

Ein Prototyp ist ein Objekt, welches Definitionen enthält, die zur Übernahme in andere Objekt gedacht sind. So müssen Definitionen, die in mehreren Objekten enthalten sein sollen nicht in jedem einzelnen Objekt wiederholt werden.

Entscheiden dafür ist es, daß das als Prototyp gedachte Objekt beim Anlegen eines neuen Objekts als Argument von »Object.create« angegeben wird. Soll kein Prototyp in das neue Objekt integriert werden, so übergibt man dort als Argument »null«.

JavaScript  sorgt dafür, daß ein Eintrag eines Prototyps eines Objekts in vielen Fällen wie ein Eintrag des Objekts selber erscheint.

Wir nennen Einträge eines Objekts auch eigene Einträge, um zu betonen, daß sie nicht aus einem Prototyp kommen, und scheinbare Einträge, wenn sie nicht wirklich im Objekt stehen, sondern nur in einem Prototyp des Objekts.

Wird ein Objekt ohne Prototyp definiert. so enthält es direkt nach der Erzeugung weder eigene noch scheinbare Einträge.

Debugger
var e = Object.create( null ); // undefined // 

Es gibt in JavaScript einige vordefinierte Objekte, die man als Prototypen verwenden kann, wie beispielsweise das Objekt »Object.prototype«.

Debugger
var o = Object.create( Object.prototype ); // undefined  // 

Nun enthält unser Objekt o scheinbar alle Funktionen, die in Wirklichkeit im Objekt »Object« stehen, während das Objekt e leer ist: es enthält keinen Prototyp und keine toString-Methode.

Debugger
e.toString()  //  /// TypeError: e.toString is not a function
o.toString(); // "[object Object]" //
Object.getPrototypeOf( e ) // null //
Object.getPrototypeOf( o ) // [object Object] //
Object.getPrototypeOf( e )=== Object.prototype // false //
Object.getPrototypeOf( o )=== Object.prototype // true //

Object.getPrototypeOf( o ) und K.prototype

Mit Object.getPrototypeOf( o ) erhält man den Prototyp p eines Objekts o (also das Objekt p, dessen Einträge scheinbar auch in o enthalten sind).

Der Eintrag K.prototype findet sich bei einem Konstruktor und wird in der Regel Objekten als Prototyp zugeordnet, welche mit jenem Konstruktor erzeugt wurde. Es handelt sich aber dabei nicht um den Prototyp des Konstruktors K (das wäre Object.getPrototypeOf( K )!), sondern um den Eintrag namens »prototyp« im Konstruktor (der gleichzeitig ein Objekt ist). Insbesondere kann man den Prototyp eines Objekts o in der Regel nicht mit o.prototyp erhalten, sondern nur mit Object.getPrototypeOf( o ).

hasOwnProperty

Wir fügen nun einen Eintrag v zum Objekt o hinzu:

Debugger
Object.defineProperty( o, "v", { value: 27, configurable: true, enumerable: false, writable: true }); // "[object Object]" //

Wir können nun sowohl »o.toString« als auch »o.v« verwenden

Debugger

o.toString // [object Function] //

o.v // 27 //

Dabei ist v direkt im Objekt eingetragen, während toString zum Prototyp gehört. Wie kann man dies feststellen. Dafür gibt es eine Methode hasOwnProperty (ebenfalls im Prototyp!), die ergibt, ob ein Schlüssel zu einem Objekt selber gehört.

Debugger
o.hasOwnProperty( 'v' ) // true // 
o.hasOwnProperty( 'mcvnswpw' ) // false //
o.hasOwnProperty( 'toString' ) // false //
Object.prototype.hasOwnProperty( 'toString' ) // true //

Mit e kann diese Methode so nicht verwendet werden, denn das e keinen Prototyp hat, ist sie über e nicht erreichbar!

Debugger
e.hasOwnProperty( 'v' ); //  /// TypeError: e.hasOwnProperty is not a function

Da es nützlich ist, die Funktion aus dem Objekt-Prototyp verwenden zu können, werden wir fürderhin Objekte in der Regel mit Object.prototype als Prototyp definieren (außer wenn sie zur bloßen Datenhaltung gedacht sind).

for (var key in obj) if (obj.hasOwnProperty(key)) f(obj,key);

Der Operator »in«

Ermöglicht Unterscheidung zwischen nicht definierten Schlüssel und Schlüsseln, welche als undefined definiert wurd.

o ={ a: undefined }; // Object { a: undefined } //

o.a === undefined // true //

o.b === undefined // true //

"a" in o // true //

"b" in o // false //

Übungsfragen

Welche Werte haben die folgenden beiden Ausdrücke vermutlich?

Ausdrücke

o.hasOwnProperty( 'hasOwnProperty' )

Object.getPrototypeOf( o ).hasOwnProperty( 'hasOwnProperty' )

Indirekte Prototypen

Betrachten wir nun Standardobjekte wie etwa Zahlenobjekte! Hat ein Zahlenobjekt Object.prototype als Protoyp?

Debugger
Object.getPrototypeOf( new Number( 2 )) // [object Number] // 
Object.getPrototypeOf( new Number( 2 )) // [object Number] //
Object.getPrototypeOf( new Number( 2 ))=== Object.prototype // false //

Nein. Bedeutet dies, daß man dann keine Object-Funktionen über ein Zahlenobjekt aufrufen kann?

Debugger
new Number( 2 ).toString() // "2" // 

Doch! Dies ist anscheinend möglich. Der Prototyp eines Zahlenobjekts ist Number.prototype.

Debugger
Object.getPrototypeOf( new Number( 2 ))=== Number.prototype // true // 

Dieser Prototyp hat seinerseits Object.prototype als Prototyp

Debugger
Object.getPrototypeOf( Object.getPrototypeOf( new Number( 2 )))=== Object.prototype // true // 
Object.getPrototypeOf( Number.prototype )=== Object.prototype // true //

Object.prototype hat seinerseits »null« als Prototyp.

Debugger
Object.getPrototypeOf( Object.getPrototypeOf( Object.getPrototypeOf( new Number( 2 )))) // null // 
Object.getPrototypeOf( Object.prototype ) // null //

Wenn nun ein Schlüssel hinter einem Objekt verwendet wird (wie in »new Number( 2 ).toString«), so wird zuerst in dem Objekt selber nach dem Schlüssel gesucht. Wurde der Schlüssel dort nicht gefunden und ist der Prototyp des Objekts nicht »null«, so wird dann in dem Prototyp des Objekts nach dem Schlüssel gesucht.

Wird der Schlüssel auch im Prototyp des Objekts nicht gefunden, so wird nachgeschaut, ob dieser Prototyp selber wiederum einen Prototyp hat, der nicht null ist, falls ja, wird dann im Prototyp des Prototyps nach dem Schlüssel gesucht. So wird immer weiter in eventuellen Prototypen gesucht, bis entweder der Schlüssel gefunden wurde, oder der Prototyp null ist.

Die Abfolge dieser Prototypen eines Objekts bis hin zu null wird auch Prototyp-Kette des Objekts genannt.

Falls auf diese Weise ein Schlüssel gefunden wurde, dann wird er so verwendet, als ob der Eintrag mit diesem Schlüssel im Objekt selber enthalten ist.

instanceof

Debugger
new String( "alpha" ) instanceof String // true //

Der Operator instanceof prüft ob constructor.prototype in der Prototypkette vorhanden ist.

Object.getPrototypeOf( o )=== String.prototype ?

Object.getPrototypeOf( Object.getPrototypeOf( o ))=== String.prototype ?

Object.getPrototypeOf( Object.getPrototypeOf( Object.getPrototypeOf( o )))=== String.prototype ?

new String( "alpha" ) hat den Objekttyp String, es ist eine Exemplar (instance) sowohl von String als auch von Objekt!

Object als weitverbreiteter Basisprototyp

Die meisten Objekte sind Exemplare von Object, daher stehen die Funktionen aus dem Objektprototyp über eine Exemplar oft zur Verfügung.

Object.prototype.toString

Wenn Textdarstellung eines Objekts benötigt wird. Diese enthält nicht immer alle wichtigen Informationen aus dem Objekt und ist auch nicht immer verständlich, es handelt sich im allgemeinen letztendlich nur um irgendeinen Text.

( {} ).toString()

"[object Object]"

^ ^
| |
Typ Klasse

Die Klasse eines Objektes ist eine Eigenschaft, die in JavaScript derzeit sonst kaum eine Rolle spielt, aber manchmal bei Verwendung von toString() sichtbar wird.

Object.prototype.toSource

Gibt es nicht in allen JavaScript-Implementationen, ist aber nützlich, um eine Objekt anzeigen zu lassen.

vgl JSON.stringify

Object.prototype.valueOf

Wandelt Objekt in entsprechenden Elementarwert (primitve value).

Definition eines Prototyps

Es ist nicht unbedingt nötig immer nur vordefinierte Prototypen zu verwenden. Wir können auch ein neues Objekt definieren, das dann als Prototyp dienen soll.

In dem folgenden Beispiel legen wir einen Prototyp »counterPrototype« an und verwenden diesen dann für zwei Objekte: »newCounter« und »otherCounter«.

Der Prototyp enthält eine Methode »inc«, die für beide Objekte verwendet werden soll. Das in jener Methode verwendete »this« steht immer für das Zielobjekt des Aufrufs, bei der Auswertung von »otherCounter.inc()« also beispielsweise für »otherCounter«.

Debugger
var counterPrototype = Object.create( Object.prototype ); // undefined  // 
Object.defineProperty( counterPrototype, "inc", { value: function(){ "use strict"; return this.v++; }, configurable: true, enumerable: false, writable: true }); // [object Object] //
var newCounter = Object.create( counterPrototype ); // undefined //
Object.defineProperty( newCounter, "v", { value: 0, configurable: true, enumerable: true, writable: true }); // [object Object] //
newCounter.inc(); // 0 //
newCounter.inc(); // 1 //
newCounter.inc(); // 2 //
var otherCounter = Object.create( counterPrototype ); // undefined //
Object.defineProperty( otherCounter, "v", { value: 0, configurable: true, enumerable: true, writable: true }); // [object Object] //
otherCounter.inc(); // 0 //
otherCounter.inc(); // 1 //
otherCounter.inc(); // 2 //
newCounter.inc(); // 3 //
newCounter.inc(); // 4 //
newCounter.inc(); // 5 //
otherCounter.inc(); // 3 //

Referenzeffekte mit Variablen

( {} === {} )

let p = new Object(); let q = p;

Kopiereffekte mit Funktionen

Debugger
function change( v ){ "use strict"; v = 5; console.log( v ); } // undefined //
let v = 0; // undefined //
change( v ); // // 5
v // 0 //

Referenzeffekte mit Funktionen

Debugger
function change( v ){ "use strict"; v.v = 5; console.log( v.v ); } // undefined //
let v ={ v: 0 }; // undefined //
change( v ); // // 5
v.v // 5 //

Kopiereffekte mit Objekten

Debugger
let alpha = { a : 1 }; // undefined // 
let beta = { x: alpha.a }; // undefined //
beta.x // 1 //
alpha.a = 2; // 2 //
beta.x // 1 //

Referenzeffekte mit Objekten

Debugger
let gamma = { v : 1 }; // undefined // 
let delta = { x: gamma }; // undefined //
gamma.v = 2; // 2 //
delta.x // Object { v: 2 } //
delta.x.v // 2 //

Unveränderbarkeit von Number/String-Objekten

Debugger
let gamma = { v : new Number( 1 )}; // undefined // 
let delta = { x: gamma.v }; // undefined //
delta.x // 1 //

Einen Schlüssel eines Objekts (also einen Namen eine Eigenschaft, oben sind beispielsweise »v« und »x« Schlüssel) nennen wir auch manchmal eine Variable.

Der Wert des Number-Objekts in der Variablen v kann nicht verändert werden, da Number-Objekte nicht veränderlich sind. Dasselbe gilt auch für String-Objekte.

Referenzeffekte mit Prototypen

Debugger
let alpha = { a : 1 };
let beta = Object.create( alpha );
alpha.a; // 1 //
beta.a; // 1 //
alpha.a = 2; /* erzeugt neue direkte Eigenschaft (own property) */
beta.a;
beta.a = 3;
alpha.a; // 2 //

Hinzufügen von Methoden zu Standard-Prototypen

Wenn wir die Methode charAt-Methode im String-Prototyp umdefinieren, so wirkt sich dies auf alle String-Objekte aus, und danach wird ein Neustart der JavaScript-Implementation nötig, weil eine wichtige Methode von uns verfälscht wurde. Daher sollte dies normalerweise vermieden werden! Wir tun dies hier nun zu Lehrzwecken:

Debugger
new String( "abc" ).charAt( 0 )   // "a"         // 
new String( "alpha" ).charAt( 0 ) // "a" //
new String( "anton" ).charAt( 0 ) // "a" //
Object.getPrototypeOf( new String( "abc" )).charAt = function( s ){ "use strict"; return "Groenland"; }; // [object Function] //
new String( "abc" ).charAt( 0 ) // "Groenland" //
new String( "alpha" ).charAt( 0 ) // "Groenland" //
new String( "anton" ).charAt( 0 ) // "Groenland" //

Das folgende Beispiel zeigt, daß »-2« kein Numerale sondern ein Operatorausdruck ist (warum?)

Debugger
2..constructor.prototype.show = function(){ return "[" + this + "]"; } // function 2.constructor.prototype.show() //
2..show() // [2] //
-2..show() // NaN //
Debugger

"abc".firstChar() // /// TypeError: "abc".firstChar is not a function

String.prototype.firstChar = function(){ return this.charAt( 0 ); } // function String.prototype.firstChar() //

"abc".firstChar() // "a" //

Debugger

Object.prototype.valueOf = function () {return 72; };

Array.prototype.valueOf = function () {return 4;};

+{}

+[]

Vereinfachtes Hinzufügen eines Eintrags

Wir haben Einträge bisher oft mit »defineProperty« zu einem Objekt hinzugefügt.

Object.defineProperty( otherCounter, "v", { value: 0, configurable: true, enumerable: true, writable: true });

Die Verwendung von »defineProperty« ist gut geeignet, um alle Details zu sehen und genau festzulegen.

In vielen Fällen kann aber auch eine vereinfachte Schreibweise verwendet werden.

Debugger
otherCounter.x = 0

Durch einen Konfigurationsdeskriptor können wir sehen, welche Konfiguration dann verwendet wird:

Debugger
var d = Object.getOwnPropertyDescriptor( otherCounter, 'x' ); // undefined // 
d.configurable // true //
d.enumerable // true //
d.writable // true //

--

[11:55:56.240] Object.getOwnPropertyNames( this )

[11:55:56.246] ["Object", "Function", "eval", "Components", "XPCNativeWrapper", "dump", "debug", "importFunction", "console", "$", "$$", "$x", "$0", "clear", "keys", "values", "help", "inspect", "inspectrules", "pprint", "print", "undefined", "Array", "Boolean", "Date", "Math", "isNaN", "isFinite", "parseFloat", "parseInt", "NaN", "Infinity", "Number", "String", "escape", "unescape", "uneval", "decodeURI", "encodeURI", "decodeURIComponent", "encodeURIComponent", "Error", "InternalError", "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError", "RegExp", "XMLList", "XML", "isXMLName", "Namespace", "QName", "Iterator", "StopIteration", "JSON", "Int8Array", "Uint8Array", "Int16Array", "Uint16Array", "Int32Array", "Uint32Array", "Float32Array", "Float64Array", "Uint8ClampedArray", "ArrayBuffer", "WeakMap"]

--

[11:56:12.073] Object.getOwnPropertyNames( 2 )

[11:56:12.082] TypeError: 2 is not an object

--

[11:56:20.829] Object.getOwnPropertyNames( 2 . __proto__ )

[11:56:20.837] ["constructor", "toSource", "toString", "toLocaleString", "valueOf", "toFixed", "toExponential", "toPrecision"]

--

[11:56:31.142] Object.getOwnPropertyNames( "abc" . __proto__ )

[11:56:31.150] ["length", "constructor", "quote", "toSource", "toString", "valueOf", "substring", "toLowerCase", "toUpperCase", "charAt", "charCodeAt", "indexOf", "lastIndexOf", "trim", "trimLeft", "trimRight", "toLocaleLowerCase", "toLocaleUpperCase", "localeCompare", "match", "search", "replace", "split", "substr", "concat", "slice", "bold", "italics", "fixed", "fontsize", "fontcolor", "link", "anchor", "strike", "small", "big", "blink", "sup", "sub"]

Vollständiges Auflisten aller eigenen Einträge (Exemplardefinitionen) eines Objekts

{ let l = Object.getOwnPropertyNames( this );
let n = l.length; for( let i = 0; i < n; ++i )console.log( l[ i ]); }

(Die Konsole einiger Debugger zeigt hier aber möglicherweise die Ausgabe nicht vollständig an, sondern versteckt einige Zeilen! about:config, devtools.hud.loglimit.console)

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Eine Verbindung zur Stefan-Ram-Startseite befindet sich oben auf dieser Seite hinter dem Text "Stefan Ram".)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. Schlüsselwörter zu dieser Seite/relevant keywords describing this page: Stefan Ram Berlin slrprd slrprd stefanramberlin spellched stefanram723191 stefan_ram:723191 Verwendung von Prototypen in JavaScript Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd723191, slrprddef723191, PbclevtugFgrsnaEnz Erklärung, Beschreibung, Info, Information, Hinweis,

Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram.
https://www.purl.org/stefan_ram/pub/call_javascript