variationen an Objekten-deklaration! Wann ist welche Sinnvoll?
mb
- javascript
1 Orlok
schönen guten Morgen,
wann ist es sinniger eines der beiden im Quellcode zunehmen???
var irgendwas = { eins : "eins", zwei : "zwei" }
oder
var irgendwas = ["eins","zwei","..."];
oder
var irgendwas = new Array();
irgendwas[0] = "irgendwer";
irgendwas[1] = "...";
Ich denke man kann alles in allen variationen lösen aber wie ist es eleganter? Danke im voraus. Liebe Grüße MB
PS: ich hab gehört das n numerisches Array auch assoziatives Array ist eben einfach n numerischer String-Index
array["nummer"] = "Wert";
Hallo
wann ist es sinniger eines der beiden im Quellcode zunehmen???
var irgendwas = { eins : "eins", zwei : "zwei" }
oder
var irgendwas = ["eins","zwei","..."];
Jeweils unter Verwendung der Literalschreibweise erzeugst du in deinem ersten Beispiel ein Objekt und in deinem zweiten Beispiel ein Array, aber wie du im Titel deines Beitrags schon bewusst (oder unbewusst) festgestellt hast, sind Arrays in JavaScript ebenfalls Objekte.
Das heißt, mit var obj = { }
erzeugst du eine Instanz von Object.prototype
und mit var arr = [ ]
eine Instanz von Array.prototype
, wobei Array
wiederum von Object
erbt.
oder
var irgendwas = new Array(); irgendwas[0] = "irgendwer"; irgendwas[1] = "...";
Hier rufst du die Funktion Array( )
als Konstruktor auf ohne Argumente zu übergeben, so dass in var irgendwas
zunächst einmal ein leeres Array gespeichert wird, dessen Indizes 0
und 1
du dann jeweils einen String
als Wert zuweist.
Abgesehen davon, dass die Verwendung des Schlüsselwortes new
hier überflüssig ist, da die Funktion Array( )
ohnehin ein neues Array als Rückgabewert hat (du hier also auch var irgendwas = Array( )
hättest schreiben können), dürfte auf den ersten Blick ersichtlich sein, dass diese Variante gegenüber der Literalnotation deutlich weniger „elegant“ ist.
Das heißt, in der Regel sollte ein Array so erzeugt werden: var arr = [ ]
, beziehungsweise direkt mit Wertübergabe: var arr = ['eins', 'zwei', 'drei']
Ich denke man kann alles in allen variationen lösen aber wie ist es eleganter?
Die Frage ist hier nicht in erster Linie, was eleganter ist, sondern was im konkreten Einzelfall benötigt wird, ein Array
oder ein Object
.
Zwar ist wie gesehen ein Array ebenfalls ein Objekt, aber Arrays verfügen über Eigenschaften und Methoden, über die plain objects nicht verfügen. Beispiel:
var arr = [1, 2, 3];
console.log(arr.length); // 3
var obj = { '1' : 1, '2' : 2, '3' : 3 };
console.log(obj.length); // undefined
Bedenke: Es gibt in JavaScript keine assoziativen Arrays. Das heißt, du kannst, weil Arrays ja auch Objekte sind, zwar schreiben…
var arr = [ ];
arr['Hallo'] = 'Welt';
…aber arr.length
bliebe bei 0
und arr[0]
bei undefined
, dass heißt, letztlich würdest du hier nur ein Array wie ein Objekt verwenden, weshalb du in diesem Fall auch gleich ein Objekt verwenden könntest und solltest.
Grundsätzlich gilt also: Hast du eine Anzahl verschiedener Werte, auf welche du über einen numerischen Index zugreifen kannst, verwende ein Array, ansonsten ein Objekt.
Gruß,
Orlok
Hallo
dazu noch eine Ergänzung:
var arr = [ ]; arr['Hallo'] = 'Welt';
…aber
arr.length
bliebe bei0
undarr[0]
beiundefined
, dass heißt, letztlich würdest du hier nur ein Array wie ein Objekt verwenden, weshalb du in diesem Fall auch gleich ein Objekt verwenden könntest und solltest.
wenn der Bezeichner eines Elements in einem Objekt (z.B.) ein Leerzeichen enthält, geht
o.ein Element
nicht,
o["ein Element"]
aber schon.
Gruß Jürgen
Tach!
dazu noch eine Ergänzung: wenn der Bezeichner eines Elements in einem Objekt (z.B.) ein Leerzeichen enthält, geht
o.ein Element
nicht,
o["ein Element"]
aber schon.
Ja, aber das zweite ist auch kein Array-Zugriff, sondern eine Alternative, wie man auf eine Eigenschaft eines Objekts zugreift, deren Namen man als Wert oder Literal vorliegen hat. Mit anderen Worten (um das nochmal zu verdeutlichen), man braucht kein Array für das was man auch assoziativen Zugriff nennt.
dedlfix.
Hallo Orlok,
Grundsätzlich gilt also: Hast du eine Anzahl verschiedener Werte, auf welche du über einen numerischen Index zugreifen kannst, verwende ein Array, ansonsten ein Objekt.
Verstanden, Danke Dir. Wie gesagt komme aus der Java Ecke.
Herzlichsen Gruß, MB
Das heißt, mit
var obj = { }
erzeugst du eine Instanz vonObject.prototype
und mitvar arr = [ ]
eine Instanz vonArray.prototype
, wobeiArray
wiederum vonObject
erbt.
Das hast du unglücklich formuliert, denn Instanzen werden in JavaScript von Konstruktor-Funktionen gebildet, nicht Objekt-Prototypen:
[1,2,3] instanceof Array.prototype // Uncaught TypeError: Expecting a function in instanceof check, but got #<Object>
[1,2,3] instanceof Array // true
Dennoch gilt natürlich, dass sowohl Array.prototype als auch Object.prototype in der Prototypkette von [1,2,3] auftauchen:
Array.prototype.isPrototypeOf([1,2,3]) // true
Object.prototype.isPrototypeOf([1,2,3]) // true
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
Würden wir hingegen das Schlüsselwort
new
bei unserem Funktionsaufruf vergessen, dann hätten wir unter Anderem ein paar neue globale Variablen produziert…
Dagegen beschützt dich der strict-Mode.
Aber als Funktionsobjekt besitzt unser Konstruktor ja auch noch eine Eigenschaft namens
prototype
, bei der es sich ebenso um ein (beinahe) leeresObject
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ügtgregor
nicht nur über die direkt in der Funktion deklarierten Eigenschaften, sondern ebenso über die Eigenschaften und Methoden, welche von uns inKaefer.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.
Interessant in dem Zusammenhang ist, dass die Methode bewerfen
bei ihrem ersten Aufruf eine neue Eigenschaft direkt auf der Instanz erzeugt, und nicht die Eigenschaft des Prototyps verändert:
var gregor = new Kaefer('gregor');
console.assert(gregor.hasOwnProperty('gesundheit') === false, 'Gesundheit ist eine direkte Eigenschaft von gregor');
gregor.bewerfen();
console.assert(gregor.hasOwnProperty('gesundheit') === true, 'Gesundheit ist keine direkte Eigenschaft von gregor');
Die Meldungen sind etwas konfus, weil sie an der Stelle Fehlermeldungen darstellen und keine Erläuterungen. Sie beschreiben also den negativen Fall, dass die Assertions fehlschlagen, nicht den positiven Fall (hier fallen beide Assertions positiv aus)
Eine unmittelbare Auswirkung ist, dass die bewerfen-Methode die Zustände anderer Kaefer-Instanzen unverändert lässt.
Nachdem wir dies also vollbracht haben, verfügt
gregor
nun über eine ganze Reihe an Eigenschaften, von denen lediglichname
,typ
undfarbe
eigene Eigenschaften sind, welche in der Konstruktorfunktion spezifiziert wurden, während der gesamte Rest über die Prototypenkette geerbt wurde.
Bis auf solche Eigenschaften, die durch Duck typing eben noch zur Laufzeit hinzukommen können, wie im oberen Fall geschildert. gesundheit
ist zunächst keine eigene Eigeschaft von Kaefer-Instanzen, wird aber dazu, sobald die bewerfen-Methode aufgerufen wird.
Hallo
Würden wir hingegen das Schlüsselwort
new
bei unserem Funktionsaufruf vergessen, dann hätten wir unter Anderem ein paar neue globale Variablen produziert…Dagegen beschützt dich der strict-Mode.
Ja, mich schon, aber nicht denjenigen, der vielleicht ohne Kenntnis dieses Features den Code aus meinen Beispielen übernimmt; Deshalb wollte ich es nicht unerwähnt lassen… ;-)
Obwohl ein entsprechender Hinweis auf den strict-Mode hier sicher nicht geschadet hätte – da hast du vollkommen recht! Eine kurze Erklärung für den interessierten Mitleser sei also nachgereicht:
Nehmen wir also einmal an, wir haben die folgende als Konstruktor bestimmte Funktion…
var Constructor = function ( ) {
this.property = 'value';
};
…und würden sie ohne das Schlüsselwort new
aufrufen…
Constructor( );
…dann würde this
auf das globale Objekt, also window
verweisen und property
wäre eine globale Variable:
console.log(property); // value
console.log(!!window.property); // true
Würden wir aber statt dessen zum Beispiel schreiben…
var Constructor = function ( ) {
'use strict';
this.property = 'value';
};
…dann würde this
beim Funktionsaufruf ohne new
nicht mehr auf window
zeigen, sondern mit undefined
initialisiert werden, wobei die Eigenschaftszuweisung dann einen type error produziert, wodurch das Programm abgebrochen und der Fehler beim Aufruf der Funktion sofort offensichtlich wird:
Constructor( ); // type error: this is undefined
Use strict!
Das also dazu. ;-)
Im Folgenden mein etwas gekürztes Ursprungsbeispiel:
var Kaefer = function (name) { this.name = name; this.typ = 'Ungeziefer'; this.farbe = 'braun'; }; Kaefer.prototype.gesundheit = 0; Kaefer.prototype.bewerfen = function ( ) { this.gesundheit -= Math.ceil(Math.random( ) * 10); }; var gregor = new Kaefer('Gregor'); gregor.bewerfen( ); var status = gregor.gesundheit; // z.B. -7 :-(
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.
Interessant in dem Zusammenhang ist, dass die Methode
bewerfen
bei ihrem ersten Aufruf eine neue Eigenschaft direkt auf der Instanz erzeugt, und nicht die Eigenschaft des Prototyps verändert […]
Vielen Dank für den Hinweis! Das ist mir im Eifer des Gefechts gar nicht aufgefallen, aber das wäre definitiv einer Erwähnung wert gewesen, zumal das nicht nur „interessant“ ist, sondern eine fundamentale Voraussetzung dafür, dass dieses Prinzip der Vererbung überhaupt zuverlässig und vor allem praktikabel funktioniert, denn wie du treffend bemerkt hast:
Eine unmittelbare Auswirkung ist, dass die bewerfen-Methode die Zustände anderer Kaefer-Instanzen unverändert lässt.
Dadurch, dass bei der Zuweisung eines Wertes zu einer geerbten Eigenschaft – in diesem Fall also der Eigenschaft gesundheit
von Kaefer.prototype
– automatisch eine neue eigene Eigenschaft der Instanz – hier also gregor
– erzeugt wird, wird sichergestellt, das sich Veränderungen nur lokal auswirken, sprich, wäre dem nicht so, müsste man immer die Herkunft einer Eigenschaft berücksichtigen und im Zweifel explizit eine neue eigene Eigenschaft definieren.
Dazu gleich noch etwas mehr.
Aber zunächst noch eine Randbemerkung zu deinem Code-Beispiel:
var gregor = new Kaefer('gregor'); console.assert(gregor.hasOwnProperty('gesundheit') === false, 'Gesundheit ist eine direkte Eigenschaft von gregor'); gregor.bewerfen(); console.assert(gregor.hasOwnProperty('gesundheit') === true, 'Gesundheit ist keine direkte Eigenschaft von gregor');
Die Meldungen sind etwas konfus, weil sie an der Stelle Fehlermeldungen darstellen und keine Erläuterungen. Sie beschreiben also den negativen Fall, dass die Assertions fehlschlagen, nicht den positiven Fall (hier fallen beide Assertions positiv aus)
Zur asynchronen Überwachung von Objekten gibt es eine bessere Methode als console.assert
, die aber leider noch nicht offizieller ECMAScript-Standard ist und die auch noch nicht browserübergreifend funktioniert, nämlich Object.observe
; Sowie zur Beendigung der Überwachung, die Schwestermethode Object.unobserve
; Siehe hierzu den entsprechenden Vorschlag.
Die Syntax: Object.observe( object, callback [, array] );
Das erste Argument ist das Objekt, welches überwacht werden soll, das zweite Argument ist eine Callback-Funktion, die bei eingetretener Veränderung aufgerufen wird, und das optionale dritte Argument ist ein Array, welches die Bezeichner der zu berücksichtigenden Veränderungen als Strings enthält.
Der Callback-Funktion wird bei ihrem Aufruf als Parameter ein Array übergeben, welches für jede erfolgte Veränderung ein Objekt enthält, dem wiederum sachdienliche Informationen entnommen werden können.
Bis dato beschränkt auf Chrome und Opera, könnte man für unseren Fall also auch schreiben:
var gregor = new Kaefer('Gregor');
Object.observe(gregor, function (changes) {
console.log('Folgende Eigenschaft wurde zum Objekt hinzugefügt: ' + changes[0].name);
}, ['add']);
gregor.bewerfen( );
Hier übergeben wir also zunächst das Instanzobjekt gregor
an die Methode und bestimmen dann, dass der Name der Veränderten Eigenschaft in die Konsole geschrieben wird, indem wir auf die name
-Eigenschaft des ersten Objektes im Array zugreifen, in welcher der Bezeichner der betroffenen Eigenschaft hinterlegt ist.
Schließlich bestimmen wir mit der Übergabe des Arrays ['add']
, dass unsere Callback-Funktion nur aufgerufen werden soll, wenn eine Eigenschaft hinzugefügt wurde. Was dann mit dem Aufruf der Methode bewerfen
auch passiert…
// Folgende Eigenschaft wurde hinzugefügt: gesundheit
Ich halte das jedenfalls für ein außerordentlich nützliches Feature und hoffe, dass es bald auch von Mozilla und Microsoft implementiert, sowie in die nächste Version des ECMAScript-Standards aufgenommen wird.
Aber gut, zurück zum eigentlichen Thema! ;-)
Wir hatten festgestellt, dass um die Invarianz vererbter Eigenschaften sicherzustellen, bei der Zuweisung (assignment) auf der Instanz automatisch eine neue eigene Eigenschaft angelegt wird.
Aber dieser Grundsatz gilt nicht immer! – Beispiel:
Object.freeze(Kaefer.prototype);
gregor.bewerfen( );
console.log(gregor.gesundheit); // 0
console.log(gregor.hasOwnProperty('gesundheit')); // false
Hier haben wir auf Kaefer.prototype
die Methode Object.freeze
angewendet, und der Aufruf der vererbten Methode bewerfen
bewirkt überhaupt nichts:
Weder verändert sich der Wert der ebenfalls geerbten Eigenschaft gesundheit
, noch wird auf der Instanz eine neue eigene Eigenschaft mit dieser Bezeichnung angelegt!
Um nun zu verstehen, was hier passiert, müssen wir erst einmal wissen, dass Objekteigenschaften in JavaScript selbst wiederum über mehrere interne Eigenschaften verfügen (um Verwechslungen zu vermeiden im Folgenden Attribute genannt), welche in einem assoziierten Objekt hinterlegt sind, welches als property descriptor bezeichnet wird:
var farbe = Object.getOwnPropertyDescriptor(gregor, 'farbe');
Mit der Methode Object.getOwnPropertyDescriptor
können wir für jede eigene Objekteigenschaft die dazugehörigen Attribute auslesen, indem wir das Objekt sowie den Bezeichner der Eigenschaft als Argumente übergeben.
Der Rückgabewert der Methode ist dann der property descriptor, welcher in unserem Beispiel für gregor.farbe
so aussehen würde:
{ value : 'braun', writable : true, enumerable : true, configurable : true } // farbe
Die Eigenschaften des property descriptors repräsentieren hier die dazugehörigen internen Eigenschaften [[Value]]
, [[Writable]]
, [[Enumerable]]
und [[Configurable]]
, wobei in diesem Zusammenhang nur das Attribut writable
von Interesse ist, welches darüber bestimmt, ob der Wert der Eigenschaft, welcher in value
hinterlegt ist, verändert werden darf.
Schauen wir uns einmal die Attribute einer Eigenschaft von Kaefer.prototype
an, auf welche wir die Methode Object.freeze
angewendet haben…
var gesundheit = Object.getOwnPropertyDescriptor(Kaefer.prototype, 'gesundheit');
{ value: 0, writable: false, enumerable: true, configurable: false } // gesundheit
…und wir sehen, dass die Attribute writable
und configurable
jeweils auf false
gesetzt wurden.
Dadurch, dass wir mit (der Holzhammermethode) Object.freeze
den Wert der Eigenschaft gesundheit
auf read-only gesetzt haben, haben wir nicht nur bewirkt, dass die Originaleigenschaft auf Kaefer.prototype
nicht mehr verändert werden kann, sondern wir haben außerdem dafür gesorgt, dass diese Eigenschaft auch nicht durch assignment, also durch bloße Zuweisung auf einer Instanz überschrieben wird.
Nebenbei sei übrigens noch erwähnt, dass im strict-Mode, bei dem Versuch eine Eigenschaft welche auf read-only gesetzt ist zu verändern, eine Fehlermeldung geworfen wird, sonst nicht!
Jedenfalls sieht es anders aus, wenn wir die entsprechende Eigenschaft nicht durch Zuweisung, sondern durch Definition hinzufügen, also vor dem Aufruf unserer Methode bewerfen
zum Beispiel folgendes schreiben:
Object.defineProperty(gregor, 'gesundheit', {
value : 0,
writable : true
});
gregor.bewerfen( );
var status = gregor.gesundheit; // z.B. -5 :-(
console.log(gregor.hasOwnProperty('gesundheit')); // true
Der Wert des Attributes writable
einer vererbten Eigenschaft bestimmt also nicht nur, ob der Wert der Eigenschaft auf dem Objekt, dessen eigene Eigenschaft sie ist, überschrieben werden darf, sondern ebenso, ob bei einem Instanzobjekt durch assignment eine neue eigene Eigenschaft angelegt wird oder nicht.
Wird eine Eigenschaft als read-only initialisiert, dann bleibt sie also über die gesamte Prototypenkette hinweg konstant, und kann auch auf einer Instanz nur durch explizite Definition überschrieben werden.
Gruß,
Orlok
var gregor = new Kaefer('gregor'); console.assert(gregor.hasOwnProperty('gesundheit') === false, 'Gesundheit ist eine direkte Eigenschaft von gregor'); gregor.bewerfen(); console.assert(gregor.hasOwnProperty('gesundheit') === true, 'Gesundheit ist keine direkte Eigenschaft von gregor');
Zur asynchronen Überwachung von Objekten gibt es eine bessere Methode als
console.assert
[…]
Meine Zielsetzung bei diesem Beispiel war es, mit JavaScripts Hausmitteln den Sachverhalt zu reflektieren, den ich zuvor in natürlicher Sprache geschildert habe. console.assert()
ist dazu da, um Fakten über das Programm zur Laufzeit zu examinieren, es ist hier ein expressives Mittel, um die Vorher- und Nachherzustände des Programms zu illustrieren. Das Object.observe()
-Beispiel zeigt das auf eine andere Art, die ich persönlich weniger suggestiv finde, anderen mag es anders ergehen, deswegen ist es trotzdem gut, dass du es gebracht hast.
Hallo
Meine Zielsetzung bei diesem Beispiel war es, mit JavaScripts Hausmitteln den Sachverhalt zu reflektieren, den ich zuvor in natürlicher Sprache geschildert habe.
Tut mir ehrlich leid. Das war idiotisch formuliert von mir! Ich wollte eigentlich nur eine zusätzliche Möglichkeit aufzeigen, wie man das auch anders hätte darstellen können.
console.assert()
ist dazu da, um Fakten über das Programm zur Laufzeit zu examinieren, es ist hier ein expressives Mittel, um die Vorher- und Nachherzustände des Programms zu illustrieren.
Im Großen und Ganzen ACK, allerdings stört mich an dieser Methode, dass in dem Fall, dass die assertion fehlschlägt, pauschal eine Fehlermeldung in die Konsole geschrieben wird, sprich, die zweifellos nützliche Funktionalität hier meiner Meinung nach unnötig mit einer bestimmten Semantik verknüpft wird.
Ich fände es besser, wenn man es hier so eingerichtet hätte, dass die Art der Ausgabe über einen zusätzlichen Parameter gesteuert werden kann. Als default value hätte man ja durchaus error festlegen können.
Davon abgesehen hoffe ich, du hast mir meine ausschweifenden Erklärungen nicht übelgenommen. ;-)
Der Threadersteller hatte ja erst kurz zuvor Fragen zu dem Thema gestellt, ohne dass wir aber dessen eigentlichen Kern in irgendeiner Form näher erläutert hätten, und da wollte ich einfach die Gelegenheit nutzen und ein paar Erklärungen nachschieben, in der Hoffnung, dass er die Beiträge vielleicht noch liest und einen Nutzen daraus ziehen kann, zumal das Thema hier ja auch nicht jeden Tag diskutiert wird.
Außerdem liegt die Zeit ja noch nicht so lange zurück, in der ich als interessierter Anfänger hier nur still mitgelesen habe, und ich erinnere mich noch gut, dass ich mich immer sehr darüber gefreut habe, wenn jemand einen bestimmten Sachverhalt einmal etwas ausführlicher beschrieben hat, so dass es mir trotz meiner begrenzten Kenntnisse der Materie dennoch möglich war, den Ausführungen zu folgen.
Ich hatte gehofft, du würdest diese Erklärungen also nicht als unangemessene Belehrungen auffassen, sondern meine eigentliche Motivation erahnen, aber da ich mir dessen nicht mehr so sicher bin, wollte ich es lieber nochmal klarstellen. ;-)
Also, sollte das falsch rüber gekommen sein, bitte ich es zu entschuldigen!
Gruß,
Orlok
Davon abgesehen hoffe ich, du hast mir meine ausschweifenden Erklärungen nicht übelgenommen. ;-)
Quatsch, diesem Forum mangelt es leider an JavaScript-KnowHow, deshalb begrüße ich deine Beiträge in jüngster Zeit umso mehr, weiter so!
@@1unitedpower
Quatsch, diesem Forum mangelt es leider an JavaScript-KnowHow.
@molily ist abhandengekommen.
LLAP 🖖
Hallo Gunnar Bittersmann,
@molily ist abhandengekommen.
Das bedauere ich sehr. Die Notification wird ihn auch nicht erreichen, denn sein letzter Beitrag stammt vom 31.7.2014 noch im alten Forum. Die alten Zugangsdaten konnten nicht übernommen werden.
Sein geänderter Twittername spricht Bände.
Bis demnächst
Matthias