Signatur
Beschreibung
Die Methode getOwnPropertyDescriptor() eines Proxy-Handlers wird immer dann aufgerufen, wenn die Engine den Eigenschafts-Deskriptor einer eigenen Eigenschaft des Proxys braucht. Das passiert nicht nur bei einem direkten Aufruf von Object.getOwnPropertyDescriptor(proxy, key) oder Reflect.getOwnPropertyDescriptor(). Auch diese Operationen lösen sie aus: Object.prototype.hasOwnProperty(), Object.hasOwn(), Object.keys(), Object.values()/Object.entries() (für jeden Schlüssel aus ownKeys), Object.assign(), der Spread-Operator {...proxy} und JSON.stringify().
Mit der Trap lassen sich Eigenschaften nach außen verstecken oder virtuelle Eigenschaften melden, die es auf dem Zielobjekt gar nicht gibt. Die Attribute vorhandener Eigenschaften lassen sich ebenfalls ändern, etwa ein Wert, der nur lesbar erscheint. Typische Einsatzfälle sind private Felder mit Präfix (_), die in Object.keys() nicht auftauchen sollen, oder berechnete Eigenschaften, die sich wie echte eigene Felder verhalten.
Die Trap muss entweder ein Objekt (den Deskriptor) oder undefined zurückgeben. undefined bedeutet „diese Eigenschaft existiert nicht“. Die Engine prüft das Ergebnis gegen feste Invarianten und wirft einen TypeError, wenn sie verletzt sind (siehe Hinweise). Am einfachsten und sichersten leitet man nicht behandelte Fälle mit Reflect.getOwnPropertyDescriptor(target, property) an das Zielobjekt weiter.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $target Pflicht | object | Das Zielobjekt des Proxys. | |
| $property Pflicht | string | symbol | Name bzw. Symbol der Eigenschaft, deren Deskriptor abgefragt wird. Zahlen kommen als String an (z. B. "0"). |
Rückgabewert
value, writable, get, set, enumerable, configurable) oder undefined, wenn die Eigenschaft als nicht vorhanden gelten soll. Andere Werte (z. B. null oder ein String) führen zu einem TypeError. Fehlende Attribute ergänzt die Engine mit false/undefined.Beispiele
Zugriffe protokollieren und weiterleiten
const user = { name: 'Anna', age: 31 };
const proxy = new Proxy(user, {
getOwnPropertyDescriptor(target, prop) {
console.log(`Deskriptor abgefragt: ${String(prop)}`);
return Reflect.getOwnPropertyDescriptor(target, prop);
}
});
console.log(Object.getOwnPropertyDescriptor(proxy, 'name'));
console.log(Object.hasOwn(proxy, 'email'));
Private Felder mit Unterstrich vor Object.keys() und JSON verstecken
const account = { id: 42, owner: 'Ben', _token: 'geheim-123' };
const isPrivate = (prop) => typeof prop === 'string' && prop.startsWith('_');
const safe = new Proxy(account, {
// ownKeys liefert _token noch, getOwnPropertyDescriptor versteckt es
getOwnPropertyDescriptor(target, prop) {
if (isPrivate(prop)) return undefined; // nur erlaubt, weil configurable: true
return Reflect.getOwnPropertyDescriptor(target, prop);
}
});
console.log(Object.keys(safe));
console.log(JSON.stringify(safe));
console.log(Object.hasOwn(safe, '_token'));
console.log(safe._token); // get-Trap nicht definiert, direkter Zugriff klappt weiterhin
Virtuelle, berechnete Eigenschaft melden
const rect = { width: 4, height: 3 };
const proxy = new Proxy(rect, {
get(target, prop, receiver) {
if (prop === 'area') return target.width * target.height;
return Reflect.get(target, prop, receiver);
},
has(target, prop) {
return prop === 'area' || Reflect.has(target, prop);
},
ownKeys(target) {
return [...Reflect.ownKeys(target), 'area'];
},
getOwnPropertyDescriptor(target, prop) {
if (prop === 'area') {
// Muss configurable: true sein, da 'area' auf dem Ziel nicht existiert
return { value: target.width * target.height, writable: false, enumerable: true, configurable: true };
}
return Reflect.getOwnPropertyDescriptor(target, prop);
}
});
console.log(Object.entries(proxy));
console.log({ ...proxy });
Invariante verletzt: nicht-konfigurierbare Eigenschaft verstecken
const config = {};
Object.defineProperty(config, 'version', { value: '1.0', configurable: false });
const proxy = new Proxy(config, {
getOwnPropertyDescriptor() {
return undefined; // versucht, 'version' zu verstecken
}
});
try {
Object.getOwnPropertyDescriptor(proxy, 'version');
} catch (err) {
console.log(err.constructor.name);
}
// Wichtig · Fallstricke
Invarianten: Bei einem Verstoß wirft die Engine einen TypeError.
- Der Rückgabewert muss ein Objekt oder
undefinedsein. - Eine nicht-konfigurierbare eigene Eigenschaft des Ziels darf nicht als nicht vorhanden (
undefined) gemeldet werden. - Ist das Ziel nicht erweiterbar (
Object.preventExtensions,seal,freeze), darf keine vorhandene Eigenschaft versteckt und keine neue erfunden werden. - Eine Eigenschaft darf nur dann als
configurable: falsegemeldet werden, wenn sie auf dem Ziel existiert und dort ebenfalls nicht konfigurierbar ist. configurable: falsezusammen mitwritable: falsedarf nur gemeldet werden, wenn die Eigenschaft auf dem Ziel ebenfalls nicht beschreibbar ist.- Der gemeldete Deskriptor muss mit der Zieleigenschaft kompatibel sein, so als würde er per
Object.definePropertyauf das Ziel angewendet.
Weitere Fallstricke:
- Die Trap versteckt eine Eigenschaft nicht vor direktem Lesen (
proxy.x) oder vor demin-Operator. Dafür braucht man zusätzlich die Trapsgetundhas. Für einheitliches Verhalten solltenownKeys,has,getundgetOwnPropertyDescriptorzueinander passen. Object.keys()ruft die Trap für jeden Schlüssel auf, denownKeysliefert, und prüft dabeienumerable. Bei großen Objekten kann das spürbar Leistung kosten.- Rückgabewerte werden normalisiert: Die Engine gibt ein neues Deskriptor-Objekt zurück, nicht dasselbe Objekt, das die Trap geliefert hat.
- Kompatibilität: Proxy gibt es in allen modernen Browsern und seit Node.js 6. In ES5-Umgebungen (z. B. IE11) fehlt es, und ein Polyfill ist nicht möglich.