Object.create(): Objekte über Deskriptoren erstellenObject.getOwnPropertyDescriptors().length von ArraysIn diesem Kapitel betrachten wir genauer, wie die ECMAScript-Spezifikation JavaScript-Objekte betrachtet. Insbesondere sind Eigenschaften in der Spezifikation nicht atomar, sondern bestehen aus mehreren Attributen (stellen Sie sich Felder in einem Record vor). Selbst der Wert einer Dateneigenschaft wird in einem Attribut gespeichert!
In der ECMAScript-Spezifikation besteht ein Objekt aus
Die Spezifikation beschreibt interne Slots wie folgt. Ich habe Aufzählungspunkte hinzugefügt und einen Teil hervorgehoben
undefined.[[ ]] eingeschlossen sind.Es gibt zwei Arten von internen Slots
Normale Objekte haben die folgenden Datenslots
.[[Prototype]]: null | object
Object.getPrototypeOf() und Object.setPrototypeOf() abgerufen werden..[[Extensible]]: boolean
Object.preventExtensions() auf false gesetzt werden..[[PrivateFieldValues]]: EntryList
Der Schlüssel einer Eigenschaft ist entweder
Es gibt zwei Arten von Eigenschaften, die sich durch ihre Attribute unterscheiden
value enthält jeden JavaScript-Wert.get, letztere im Attribut set gespeichert.Zusätzlich gibt es Attribute, die beide Arten von Eigenschaften haben. Die folgende Tabelle listet alle Attribute und ihre Standardwerte auf.
| Art der Eigenschaft | Name und Typ des Attributs | Standardwert |
|---|---|---|
| Dateneigenschaft | value: any |
undefined |
writable: boolean |
false |
|
| Zugriffseigenschaft | get: (this: any) => any |
undefined |
set: (this: any, v: any) => void |
undefined |
|
| Alle Eigenschaften | configurable: boolean |
false |
enumerable: boolean |
false |
Wir sind bereits auf die Attribute value, get und set gestoßen. Die anderen Attribute funktionieren wie folgt
writable bestimmt, ob der Wert einer Dateneigenschaft geändert werden kann.configurable bestimmt, ob die Attribute einer Eigenschaft geändert werden können. Wenn es false ist, dannvalue ändern.writable von true auf false ändern. Die Begründung für diese Anomalie ist historisch: Die Eigenschaft .length von Arrays war schon immer beschreibbar und nicht konfigurierbar. Die Möglichkeit, ihr writable-Attribut zu ändern, ermöglicht es uns, Arrays einzufrieren.enumerable beeinflusst einige Operationen (wie Object.keys()). Wenn es false ist, ignorieren diese Operationen die Eigenschaft. Die meisten Eigenschaften sind aufzählbar (z. B. solche, die per Zuweisung oder Objektliterale erstellt wurden), weshalb Sie dieses Attribut in der Praxis selten bemerken werden. Wenn Sie sich weiterhin dafür interessieren, wie es funktioniert, siehe §12 „Aufzählbarkeit von Eigenschaften“.Wenn eine vererbte Eigenschaft nicht beschreibbar ist, können wir keine eigene Eigenschaft mit demselben Schlüssel per Zuweisung erstellen
const proto = {
prop: 1,
};
// Make proto.prop non-writable:
Object.defineProperty(
proto, 'prop', {writable: false});
const obj = Object.create(proto);
assert.throws(
() => obj.prop = 2,
/^TypeError: Cannot assign to read only property 'prop'/);Weitere Informationen finden Sie unter §11.3.4 „Vererbte schreibgeschützte Eigenschaften verhindern die Erstellung eigener Eigenschaften per Zuweisung“.
Ein Eigenschaftsdeskriptor kodiert die Attribute einer Eigenschaft als JavaScript-Objekt. Ihre TypeScript-Interfaces sehen wie folgt aus.
interface DataPropertyDescriptor {
value?: any;
writable?: boolean;
configurable?: boolean;
enumerable?: boolean;
}
interface AccessorPropertyDescriptor {
get?: (this: any) => any;
set?: (this: any, v: any) => void;
configurable?: boolean;
enumerable?: boolean;
}
type PropertyDescriptor = DataPropertyDescriptor | AccessorPropertyDescriptor;Die Fragezeichen zeigen an, dass alle Eigenschaften optional sind. §9.7 „Weglassen von Deskriptoreigenschaften“ beschreibt, was passiert, wenn sie weggelassen werden.
Object.getOwnPropertyDescriptor(): Deskriptor für eine einzelne Eigenschaft abrufenBetrachten Sie das folgende Objekt
const legoBrick = {
kind: 'Plate 1x3',
color: 'yellow',
get description() {
return `${this.kind} (${this.color})`;
},
};Holen wir uns zuerst einen Deskriptor für die Dateneigenschaft .color
assert.deepEqual(
Object.getOwnPropertyDescriptor(legoBrick, 'color'),
{
value: 'yellow',
writable: true,
enumerable: true,
configurable: true,
});So sieht der Deskriptor für die Zugriffseigenschaft .description aus
const desc = Object.getOwnPropertyDescriptor.bind(Object);
assert.deepEqual(
Object.getOwnPropertyDescriptor(legoBrick, 'description'),
{
get: desc(legoBrick, 'description').get, // (A)
set: undefined,
enumerable: true,
configurable: true
});Die Verwendung der Hilfsfunktion desc() in Zeile A stellt sicher, dass .deepEqual() funktioniert.
Object.getOwnPropertyDescriptors(): Deskriptoren für alle Eigenschaften eines Objekts abrufenconst legoBrick = {
kind: 'Plate 1x3',
color: 'yellow',
get description() {
return `${this.kind} (${this.color})`;
},
};
const desc = Object.getOwnPropertyDescriptor.bind(Object);
assert.deepEqual(
Object.getOwnPropertyDescriptors(legoBrick),
{
kind: {
value: 'Plate 1x3',
writable: true,
enumerable: true,
configurable: true,
},
color: {
value: 'yellow',
writable: true,
enumerable: true,
configurable: true,
},
description: {
get: desc(legoBrick, 'description').get, // (A)
set: undefined,
enumerable: true,
configurable: true,
},
});Die Verwendung der Hilfsfunktion desc() in Zeile A stellt sicher, dass .deepEqual() funktioniert.
Wenn wir eine Eigenschaft mit dem Schlüssel k über einen Eigenschaftsdeskriptor propDesc definieren, hängt das Geschehen
k vorhanden ist, wird eine neue eigene Eigenschaft erstellt, die die durch propDesc angegebenen Attribute hat.k vorhanden ist, ändert die Definition die Attribute der Eigenschaft so, dass sie propDesc entsprechen.Object.defineProperty(): Einzelne Eigenschaften über Deskriptoren definierenZuerst erstellen wir eine neue Eigenschaft über einen Deskriptor
const car = {};
Object.defineProperty(car, 'color', {
value: 'blue',
writable: true,
enumerable: true,
configurable: true,
});
assert.deepEqual(
car,
{
color: 'blue',
});Als Nächstes ändern wir die Art einer Eigenschaft über einen Deskriptor; wir wandeln eine Dateneigenschaft in einen Getter um
const car = {
color: 'blue',
};
let readCount = 0;
Object.defineProperty(car, 'color', {
get() {
readCount++;
return 'red';
},
});
assert.equal(car.color, 'red');
assert.equal(readCount, 1);Schließlich ändern wir den Wert einer Dateneigenschaft über einen Deskriptor
const car = {
color: 'blue',
};
// Use the same attributes as assignment:
Object.defineProperty(
car, 'color', {
value: 'green',
writable: true,
enumerable: true,
configurable: true,
});
assert.deepEqual(
car,
{
color: 'green',
});Wir haben dieselben Eigenschaftenattribute wie bei der Zuweisung verwendet.
Object.defineProperties(): Mehrere Eigenschaften über Deskriptoren definierenObject.defineProperties() ist die Multi-Property-Version von `Object.defineProperty()
const legoBrick1 = {};
Object.defineProperties(
legoBrick1,
{
kind: {
value: 'Plate 1x3',
writable: true,
enumerable: true,
configurable: true,
},
color: {
value: 'yellow',
writable: true,
enumerable: true,
configurable: true,
},
description: {
get: function () {
return `${this.kind} (${this.color})`;
},
enumerable: true,
configurable: true,
},
});
assert.deepEqual(
legoBrick1,
{
kind: 'Plate 1x3',
color: 'yellow',
get description() {
return `${this.kind} (${this.color})`;
},
});Object.create(): Objekte über Deskriptoren erstellenObject.create() erstellt ein neues Objekt. Sein erstes Argument gibt den Prototyp dieses Objekts an. Sein optionales zweites Argument gibt Deskriptoren für die Eigenschaften dieses Objekts an. Im nächsten Beispiel erstellen wir dasselbe Objekt wie im vorherigen Beispiel.
const legoBrick2 = Object.create(
Object.prototype,
{
kind: {
value: 'Plate 1x3',
writable: true,
enumerable: true,
configurable: true,
},
color: {
value: 'yellow',
writable: true,
enumerable: true,
configurable: true,
},
description: {
get: function () {
return `${this.kind} (${this.color})`;
},
enumerable: true,
configurable: true,
},
});
// Did we really create the same object?
assert.deepEqual(legoBrick1, legoBrick2); // Yes!Object.getOwnPropertyDescriptors()Object.getOwnPropertyDescriptors() hilft uns bei zwei Anwendungsfällen, wenn wir es mit Object.defineProperties() oder Object.create() kombinieren.
Seit ES6 verfügt JavaScript bereits über eine Tool-Methode zum Kopieren von Eigenschaften: Object.assign(). Diese Methode verwendet jedoch einfache Lese- und Schreibvorgänge, um eine Eigenschaft mit dem Schlüssel key zu kopieren
Das bedeutet, dass sie nur eine exakte Kopie einer Eigenschaft erstellt, wenn
writable ist true und ihr Attribut enumerable ist true (da per Zuweisung Eigenschaften erstellt werden).Das folgende Beispiel verdeutlicht diese Einschränkung. Objekt source hat einen Setter mit dem Schlüssel data.
const source = {
set data(value) {
this._data = value;
}
};
// Property `data` exists because there is only a setter
// but has the value `undefined`.
assert.equal('data' in source, true);
assert.equal(source.data, undefined);Wenn wir Object.assign() verwenden, um die Eigenschaft data zu kopieren, wird die Zugriffseigenschaft data in eine Dateneigenschaft umgewandelt
const target1 = {};
Object.assign(target1, source);
assert.deepEqual(
Object.getOwnPropertyDescriptor(target1, 'data'),
{
value: undefined,
writable: true,
enumerable: true,
configurable: true,
});
// For comparison, the original:
const desc = Object.getOwnPropertyDescriptor.bind(Object);
assert.deepEqual(
Object.getOwnPropertyDescriptor(source, 'data'),
{
get: undefined,
set: desc(source, 'data').set,
enumerable: true,
configurable: true,
});Glücklicherweise kopiert die Verwendung von Object.getOwnPropertyDescriptors() zusammen mit Object.defineProperties() die Eigenschaft data exakt
const target2 = {};
Object.defineProperties(
target2, Object.getOwnPropertyDescriptors(source));
assert.deepEqual(
Object.getOwnPropertyDescriptor(target2, 'data'),
{
get: undefined,
set: desc(source, 'data').set,
enumerable: true,
configurable: true,
});super verwendenEine Methode, die super verwendet, ist fest mit ihrem Home-Objekt (dem Objekt, in dem sie gespeichert ist) verbunden. Derzeit gibt es keine Möglichkeit, eine solche Methode in ein anderes Objekt zu kopieren oder zu verschieben.
Object.getOwnPropertyDescriptors(): Objekte klonenShallow Cloning ähnelt dem Kopieren von Eigenschaften, weshalb Object.getOwnPropertyDescriptors() auch hier eine gute Wahl ist.
Um den Klon zu erstellen, verwenden wir Object.create()
const original = {
set data(value) {
this._data = value;
}
};
const clone = Object.create(
Object.getPrototypeOf(original),
Object.getOwnPropertyDescriptors(original));
assert.deepEqual(original, clone);Weitere Informationen zu diesem Thema finden Sie unter §6 „Kopieren von Objekten und Arrays“.
Alle Eigenschaften von Deskriptoren sind optional. Was passiert, wenn Sie eine Eigenschaft weglassen, hängt von der Operation ab.
Wenn wir eine neue Eigenschaft über einen Deskriptor erstellen, bedeutet das Weglassen von Attributen, dass ihre Standardwerte verwendet werden
const car = {};
Object.defineProperty(
car, 'color', {
value: 'red',
});
assert.deepEqual(
Object.getOwnPropertyDescriptor(car, 'color'),
{
value: 'red',
writable: false,
enumerable: false,
configurable: false,
});Wenn wir stattdessen eine bestehende Eigenschaft ändern, bedeutet das Weglassen von Deskriptoreigenschaften, dass die entsprechenden Attribute nicht berührt werden
const car = {
color: 'yellow',
};
assert.deepEqual(
Object.getOwnPropertyDescriptor(car, 'color'),
{
value: 'yellow',
writable: true,
enumerable: true,
configurable: true,
});
Object.defineProperty(
car, 'color', {
value: 'pink',
});
assert.deepEqual(
Object.getOwnPropertyDescriptor(car, 'color'),
{
value: 'pink',
writable: true,
enumerable: true,
configurable: true,
});Die allgemeine Regel (mit wenigen Ausnahmen) für Eigenschaftenattribute ist
Eigenschaften von Objekten am Anfang einer Prototypenkette sind normalerweise beschreibbar, aufzählbar und konfigurierbar.
Wie im Kapitel über Aufzählbarkeit beschrieben, sind die meisten vererbten Eigenschaften nicht aufzählbar, um sie vor älteren Konstrukten wie for-in-Schleifen zu verbergen. Vererbte Eigenschaften sind normalerweise beschreibbar und konfigurierbar.
const obj = {};
obj.prop = 3;
assert.deepEqual(
Object.getOwnPropertyDescriptors(obj),
{
prop: {
value: 3,
writable: true,
enumerable: true,
configurable: true,
}
});const obj = { prop: 'yes' };
assert.deepEqual(
Object.getOwnPropertyDescriptors(obj),
{
prop: {
value: 'yes',
writable: true,
enumerable: true,
configurable: true
}
});.length von ArraysDie eigene Eigenschaft .length von Arrays ist nicht aufzählbar, damit sie nicht von Object.assign(), Spread-Syntax und ähnlichen Operationen kopiert wird. Sie ist auch nicht konfigurierbar
> Object.getOwnPropertyDescriptor([], 'length')
{ value: 0, writable: true, enumerable: false, configurable: false }
> Object.getOwnPropertyDescriptor('abc', 'length')
{ value: 3, writable: false, enumerable: false, configurable: false }.length ist eine spezielle Dateneigenschaft, insofern als sie von anderen eigenen Eigenschaften (insbesondere Index-Eigenschaften) beeinflusst wird (und diese beeinflusst).
assert.deepEqual(
Object.getOwnPropertyDescriptor(Array.prototype, 'map'),
{
value: Array.prototype.map,
writable: true,
enumerable: false,
configurable: true
});class DataContainer {
accessCount = 0;
constructor(data) {
this.data = data;
}
getData() {
this.accessCount++;
return this.data;
}
}
assert.deepEqual(
Object.getOwnPropertyDescriptors(DataContainer.prototype),
{
constructor: {
value: DataContainer,
writable: true,
enumerable: false,
configurable: true,
},
getData: {
value: DataContainer.prototype.getData,
writable: true,
enumerable: false,
configurable: true,
}
});Beachten Sie, dass alle eigenen Eigenschaften von Instanzen von DataContainer beschreibbar, aufzählbar und konfigurierbar sind
const dc = new DataContainer('abc')
assert.deepEqual(
Object.getOwnPropertyDescriptors(dc),
{
accessCount: {
value: 0,
writable: true,
enumerable: true,
configurable: true,
},
data: {
value: 'abc',
writable: true,
enumerable: true,
configurable: true,
}
});Die folgenden Tool-Methoden verwenden Eigenschaftsdeskriptoren
Object.defineProperty(obj: object, key: string|symbol, propDesc: PropertyDescriptor): object [ES5]
Erstellt oder ändert eine Eigenschaft auf obj, deren Schlüssel key ist und deren Attribute über propDesc spezifiziert werden. Gibt das geänderte Objekt zurück.
Object.defineProperties(obj: object, properties: {[k: string|symbol]: PropertyDescriptor}): object [ES5]
Die Batch-Version von Object.defineProperty(). Jede Eigenschaft p des Objekts properties spezifiziert eine Eigenschaft, die zu obj hinzugefügt werden soll: Der Schlüssel von p spezifiziert den Schlüssel der Eigenschaft, der Wert von p ist ein Deskriptor, der die Attribute der Eigenschaft spezifiziert.
Object.create(proto: null|object, properties?: {[k: string|symbol]: PropertyDescriptor}): object [ES5]
Zuerst wird ein Objekt erstellt, dessen Prototyp proto ist. Dann, wenn der optionale Parameter properties bereitgestellt wurde, werden diesem Eigenschaften hinzugefügt – auf dieselbe Weise wie Object.defineProperties(). Abschließend wird das Ergebnis zurückgegeben. Zum Beispiel erzeugt der folgende Code-Schnipsel dasselbe Ergebnis wie der vorherige Schnipsel
Object.getOwnPropertyDescriptor(obj: object, key: string|symbol): undefined|PropertyDescriptor [ES5]
Gibt den Deskriptor der eigenen (nicht vererbten) Eigenschaft von obj zurück, deren Schlüssel key ist. Wenn keine solche Eigenschaft vorhanden ist, wird undefined zurückgegeben.
Object.getOwnPropertyDescriptors(obj: object): {[k: string|symbol]: PropertyDescriptor} [ES2017]
Gibt ein Objekt zurück, in dem jeder Eigenschaftsschlüssel 'k' von obj auf den Eigenschaftsdeskriptor für obj.k abgebildet wird. Das Ergebnis kann als Eingabe für Object.defineProperties() und Object.create() verwendet werden.
const propertyKey = Symbol('propertyKey');
const obj = {
[propertyKey]: 'abc',
get count() { return 123 },
};
const desc = Object.getOwnPropertyDescriptor.bind(Object);
assert.deepEqual(
Object.getOwnPropertyDescriptors(obj),
{
[propertyKey]: {
value: 'abc',
writable: true,
enumerable: true,
configurable: true
},
count: {
get: desc(obj, 'count').get, // (A)
set: undefined,
enumerable: true,
configurable: true
}
});Die Verwendung von desc() in Zeile A ist ein Workaround, damit .deepEqual() funktioniert.
Die nächsten drei Kapitel geben weitere Details zu Eigenschaftenattributen