Start · Sprachen · JavaScript · Referenz · handler.getOwnPropertyDescriptor()

handler.getOwnPropertyDescriptor()

Methode

Trap für die interne Methode <code>[[GetOwnProperty]]</code>; fängt u. a. <code>Object.getOwnPropertyDescriptor()</code>, <code>Object.hasOwn()</code> und <code>Object.keys()</code> ab.

seit JavaScript ES2015 Kategorie: method-property

Signatur

new Proxy(target, { getOwnPropertyDescriptor(target, property) { … } })

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

Typ
PropertyDescriptor | undefined
Beschreibung
Ein Deskriptor-Objekt (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'));
Deskriptor abgefragt: name { value: 'Anna', writable: true, enumerable: true, configurable: true } Deskriptor abgefragt: email false

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
[ 'id', 'owner' ] {"id":42,"owner":"Ben"} false geheim-123

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 });
[ [ 'width', 4 ], [ 'height', 3 ], [ 'area', 12 ] ] { width: 4, height: 3, area: 12 }

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);
}
TypeError

// Wichtig · Fallstricke

Invarianten: Bei einem Verstoß wirft die Engine einen TypeError.

  • Der Rückgabewert muss ein Objekt oder undefined sein.
  • 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: false gemeldet werden, wenn sie auf dem Ziel existiert und dort ebenfalls nicht konfigurierbar ist.
  • configurable: false zusammen mit writable: false darf 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.defineProperty auf das Ziel angewendet.

Weitere Fallstricke:

  • Die Trap versteckt eine Eigenschaft nicht vor direktem Lesen (proxy.x) oder vor dem in-Operator. Dafür braucht man zusätzlich die Traps get und has. Für einheitliches Verhalten sollten ownKeys, has, get und getOwnPropertyDescriptor zueinander passen.
  • Object.keys() ruft die Trap für jeden Schlüssel auf, den ownKeys liefert, und prüft dabei enumerable. 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.