In diesem Blogbeitrag werfen wir einen genaueren Blick darauf, wie die ECMAScript-Spezifikation JavaScript-Objekte sieht. Insbesondere sind Eigenschaften in der Spezifikation nicht atomar, sondern bestehen aus mehreren Attributen (denken Sie an Felder in einem Datensatz). Sogar der Wert einer Dateneigenschaft wird in einem Attribut gespeichert!
Inhaltsverzeichnis:
- Die Struktur von Objekten #
- Interne Slots #
- Property keys #
- Property attributes #
- Property descriptors #
- Deskriptoren für Eigenschaften abrufen #
- Erstellen neuer Eigenschaften über Deskriptoren #
- Ändern vorhandener Eigenschaften über Deskriptoren #
- Fallstrick: Geerbte schreibgeschützte Eigenschaften können nicht zugewiesen werden #
- API: property descriptors #
- Anwendungsfälle für Object .getOwnPropertyDescriptors() #
- Objekt.getOwnPropertyDescriptors(): Kopieren von Eigenschaften in ein Objekt #
- Pitfall: kopieren von Methoden, die super #
- Objekt.getOwnPropertyDescriptors(): cloning objects #
Die Struktur von Objekten #
In der ECMAScript-Spezifikation besteht ein Objekt aus:
- Interne Steckplätze, die Speicherorte sind, auf die von JavaScript aus nicht zugegriffen werden kann, nur für Vorgänge in der Spezifikation.
- Eine Sammlung von Eigenschaften. Jede Eigenschaft verknüpft einen Schlüssel mit Attributen (denken Sie an Felder in einem Datensatz).
Interne Slots #
So beschreibt die Spezifikation interne Slots (die Betonung liegt bei mir):
- Interne Slots entsprechen dem internen Status, der Objekten zugeordnet ist und von verschiedenen ECMAScript-Spezifikationsalgorithmen verwendet wird.
- Interne Slots sind keine Objekteigenschaften und werden nicht vererbt.
- Abhängig von der spezifischen internen Steckplatzspezifikation kann ein solcher Status aus folgenden Werten bestehen:
- eines beliebigen ECMAScript-Sprachtyps oder
- bestimmter ECMAScript-Spezifikationstypwerte.
- Sofern nicht ausdrücklich anders angegeben, werden interne Slots als Teil des Erstellungsprozesses eines Objekts zugewiesen und dürfen einem Objekt nicht dynamisch hinzugefügt werden.
- Sofern nicht anders angegeben, ist der Anfangswert eines internen Steckplatzes der Wert
undefined
. - Verschiedene Algorithmen innerhalb dieser Spezifikation erzeugen Objekte mit internen Slots. Die ECMAScript-Sprache bietet jedoch keine direkte Möglichkeit, interne Slots einem Objekt zuzuordnen.
- Interne Methoden und interne Slots werden innerhalb dieser Spezifikation mit Namen in doppelten eckigen Klammern
]
identifiziert.
Es gibt zwei Arten von internen Slots:
- Methodensteckplätze zum Bearbeiten von Objekten (Abrufen von Eigenschaften, Festlegen von Eigenschaften usw.)
- Data slots with storage (listed in the table below)
Internal data slot | Type |
---|---|
] |
null | object |
] |
boolean |
] |
List of entries |
Descriptions for these data schlüsselwörter:
Property keys #
Der Schlüssel einer Eigenschaft ist entweder:
- Ein String
- Ein Symbol
Property attributes #
Es gibt zwei Arten von Eigenschaften und sie haben unterschiedliche Attribute:
- Eine Dateneigenschaft speichert Daten. Seine Attribute
value
enthält einen beliebigen JavaScript-Wert. - Eine Accessor-Eigenschaft hat eine Getter-Funktion und/oder eine Setter-Funktion. Ersteres wird im Attribut
get
gespeichert, letzteres im Attributset
.
Die folgende Tabelle listet alle Eigenschaftsattribute auf.
Kind of property | Name and type of attribute | Default value |
---|---|---|
Data property | value: any |
undefined |
writable: boolean |
false |
|
Accessor property | get(): any |
undefined |
set(v: any): void |
undefined |
|
All properties | configurable: boolean |
false |
enumerable: boolean |
false |
We have already encountered the attributes value
get
, and set
. The other attributes work as follows:
-
writable
bestimmt, ob der Wert einer Dateneigenschaft geändert werden kann. -
configurable
bestimmt, ob die Attribute einer Eigenschaft geändert werden können. Wenn esfalse
, dann:- Sie können die Eigenschaft nicht löschen.
- Sie können eine Eigenschaft nicht von einer Dateneigenschaft in eine Accessor-Eigenschaft oder umgekehrt ändern.
- Sie können kein anderes Attribut als
value
ändern. - Eine weitere Attributänderung ist jedoch zulässig: Sie können
writable
vontrue
infalse
ändern. Der Grund für diese Anomalie ist historisch: Eigenschaft.length
von Arrays war immer beschreibbar und nicht konfigurierbar. Wenn wir zulassen, dass das Attributwritable
geändert wird, können wir Arrays einfrieren.
-
enumerable
beeinflusst einige Operationen (wieObject.assign()
). Wenn esfalse
, ignorieren diese Vorgänge die Eigenschaft.
Property descriptors #
Ein Property Descriptor codiert die Attribute einer Eigenschaft als JavaScript-Objekt. Ihre TypeScript-Schnittstellen sehen wie folgt aus.
Die Fragezeichen zeigen an, dass jede Eigenschaft optional ist. Wenn Sie eine Eigenschaft weglassen, wenn Sie einen Deskriptor an eine Operation übergeben, wird der Standardwert verwendet.
Deskriptoren für Eigenschaften abrufen #
Der folgende Code ruft den Objektdeskriptor für die Dateneigenschaft ab first
:
Im nächsten Beispiel rufen wir den Eigenschaftsdeskriptor für den Getter fullName
ab:
Die Verwendung von desc()
in Zeile A ist eine Problemumgehung, sodass .deepEqual()
funktioniert.
Erstellen neuer Eigenschaften über Deskriptoren #
Sie können auch neue Eigenschaften über Eigenschaftendeskriptoren erstellen:
Ändern vorhandener Eigenschaften über Deskriptoren #
Wenn bereits eine eigene Eigenschaft vorhanden ist, ändert die Definition über einen Deskriptor diese Eigenschaft. Auf der einen Seite können wir Object.defineProperty()
wie folgt verwenden:
Andererseits können wir auch Object.defineProperty()
verwenden, um eine Dateneigenschaft in einen Getter umzuwandeln (und umgekehrt):
Fallstrick: Geerbte schreibgeschützte Eigenschaften können nicht zugewiesen werden #
Wenn eine geerbte Eigenschaft schreibgeschützt ist, können wir sie nicht mit der Zuweisung ändern. Der Grund dafür ist, dass das Überschreiben einer geerbten Eigenschaft durch Erstellen einer eigenen Eigenschaft als zerstörungsfreie Änderung der geerbten Eigenschaft angesehen werden kann. Wenn eine Eigenschaft nicht beschreibbar ist, sollten wir das wohl nicht können.
Schauen wir uns ein Beispiel an:
Wir können die Eigenschaft nicht über Zuweisung ändern. Aber wir können immer noch eine eigene Eigenschaft erstellen, indem wir sie definieren:
Object.defineProperty(obj, 'prop', {value: 2});assert.equal(obj.prop, 2);
Accessor-Eigenschaften, die keinen Setter haben, gelten auch als schreibgeschützt:
API: property descriptors #
Mit den folgenden Funktionen können Sie mit Eigenschaftendeskriptoren arbeiten:
Anwendungsfälle für Object .getOwnPropertyDescriptors() #
Objekt.getOwnPropertyDescriptors(): Kopieren von Eigenschaften in ein Objekt #
Seit ES6 hat JavaScript bereits eine Werkzeugmethode zum Kopieren von Eigenschaften: Object.assign()
. Diese Methode verwendet jedoch einfache Get- und set-Operationen, um eine Eigenschaft zu kopieren, deren Schlüssel key
lautet:
target = source;
Dies bedeutet, dass nur dann eine originalgetreue Kopie einer Eigenschaft erstellt wird, wenn:
- Das Attribut
writable
true
und sein Attributenumerable
isttrue
(denn so erstellt die Zuweisung Eigenschaften). - Es ist eine Dateneigenschaft.
Das folgende Beispiel veranschaulicht diese Einschränkung. Objekt source
hat einen Setter, dessen Schlüssel data
.
Wenn wir Object.assign()
verwenden, um die Eigenschaft data
zu kopieren, wird die Accessor-Eigenschaft data
in eine Dateneigenschaft konvertiert:
Glücklicherweise wird Object.getOwnPropertyDescriptors()
zusammen mit Object.defineProperties()
kopiert die Eigenschaft getreu data
:
Pitfall: kopieren von Methoden, die super
#
verwendenEine Methode, die super
verwendet, ist fest mit ihrem Home-Objekt verbunden (dem Objekt, in dem sie gespeichert ist). Derzeit gibt es keine Möglichkeit, eine solche Methode in ein anderes Objekt zu kopieren oder zu verschieben.
Objekt.getOwnPropertyDescriptors(): cloning objects #
Das flache Klonen ähnelt dem Kopieren von Eigenschaften, weshalb Object.getOwnPropertyDescriptors()
auch hier eine gute Wahl ist.
Um den Klon zu erstellen, verwenden wir Object.create()
: