Metaprogrammierung

Die Objekte Proxy und Reflect ermöglichen es Ihnen, grundlegende Sprachoperationen (z.B. Eigenschaftssuche, Zuweisung, Auflistung, Funktionsaufruf usw.) abzufangen und benutzerdefiniertes Verhalten zu definieren. Mit Hilfe dieser beiden Objekte können Sie auf der Metabene von JavaScript programmieren.

Proxies

Proxy-Objekte ermöglichen es Ihnen, bestimmte Operationen abzufangen und benutzerdefinierte Verhaltensweisen zu implementieren.

Zum Beispiel das Abrufen einer Eigenschaft in einem Objekt:

js
const handler = {
  get(target, name) {
    return name in target ? target[name] : 42;
  },
};

const p = new Proxy({}, handler);
p.a = 1;
console.log(p.a, p.b); // 1, 42

Das Proxy-Objekt definiert ein target (hier ein leeres Objekt) und ein handler-Objekt, in dem eine get-Fangvorrichtung (trap) implementiert ist. Hierbei gibt ein durch einen Proxy ersetztes Objekt nicht undefined zurück, wenn undefinierte Eigenschaften abgerufen werden, sondern gibt stattdessen die Zahl 42 zurück.

Zusätzliche Beispiele finden Sie auf der Proxy-Referenzseite.

Terminologie

Die folgenden Begriffe werden verwendet, wenn über die Funktionalität von Proxies gesprochen wird.

handler

Platzhalterobjekt, das Fangvorrichtungen enthält.

traps

Die Methoden, die den Zugriff auf Eigenschaften bereitstellen. (Dies entspricht dem Konzept von traps in Betriebssystemen.)

target

Objekt, das der Proxy virtualisiert. Es wird häufig als Speicher-Backend für den Proxy verwendet. Invarianten (Semantiken, die unverändert bleiben) in Bezug auf die Nicht-Erweiterbarkeit oder nicht konfigurierbare Eigenschaften von Objekten werden gegen das Zielobjekt verifiziert.

invariants

Semantiken, die unverändert bleiben, wenn benutzerdefinierte Operationen implementiert werden, werden Invarianten genannt. Bei einem Verstoß gegen die Invarianten eines Handlers wird ein TypeError ausgelöst.

Handler und Traps

Die folgende Tabelle fasst die verfügbaren Traps zusammen, die Proxy-Objekten zur Verfügung stehen. Sehen Sie sich die Referenzseiten für detaillierte Erklärungen und Beispiele an.

Handler / Trap Abfangmaßnahmen
handler.getPrototypeOf() Object.getPrototypeOf()
Reflect.getPrototypeOf()
__proto__
Object.prototype.isPrototypeOf()
instanceof
handler.setPrototypeOf() Object.setPrototypeOf()
Reflect.setPrototypeOf()
handler.isExtensible() Object.isExtensible()
Reflect.isExtensible()
handler.preventExtensions() Object.preventExtensions()
Reflect.preventExtensions()
handler.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor()
Reflect.getOwnPropertyDescriptor()
handler.defineProperty() Object.defineProperty()
Reflect.defineProperty()
handler.has()
Abfrage von Eigenschaften
foo in proxy
Abfrage geerbter Eigenschaften
foo in Object.create(proxy)
Reflect.has()
handler.get()
Zugriff auf Eigenschaften
proxy[foo]
proxy.bar
Zugriff auf geerbte Eigenschaften
Object.create(proxy)[foo]
Reflect.get()
handler.set()
Zuweisung von Eigenschaften
proxy[foo] = bar
proxy.foo = bar
Zuweisung geerbter Eigenschaften
Object.create(proxy)[foo] = bar
Reflect.set()
handler.deleteProperty()
Löschen von Eigenschaften
delete proxy[foo]
delete proxy.foo
Reflect.deleteProperty()
handler.ownKeys() Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
Reflect.ownKeys()
handler.apply() proxy(..args)
Function.prototype.apply() und Function.prototype.call()
Reflect.apply()
handler.construct() new proxy(...args)
Reflect.construct()

Widerrufbarer Proxy

Die Proxy.revocable()-Methode wird verwendet, um ein widerrufbares Proxy-Objekt zu erstellen. Dies bedeutet, dass der Proxy über die Funktion revoke widerrufen werden kann und den Proxy ausschaltet.

Danach führt jede Operation auf dem Proxy zu einem TypeError.

js
const revocable = Proxy.revocable(
  {},
  {
    get(target, name) {
      return `[[${name}]]`;
    },
  },
);
const proxy = revocable.proxy;
console.log(proxy.foo); // "[[foo]]"

revocable.revoke();

console.log(proxy.foo); // TypeError: Cannot perform 'get' on a proxy that has been revoked
proxy.foo = 1; // TypeError: Cannot perform 'set' on a proxy that has been revoked
delete proxy.foo; // TypeError: Cannot perform 'deleteProperty' on a proxy that has been revoked
console.log(typeof proxy); // "object", typeof doesn't trigger any trap

Reflexion

Reflect ist ein eingebautes Objekt, das Methoden für abfangbare JavaScript-Operationen bereitstellt. Die Methoden sind die gleichen wie die des Proxy-Handlers.

Reflect ist kein Funktionsobjekt.

Reflect hilft beim Weiterleiten standardmäßiger Operationen vom Handler an das target.

Mit Reflect.has() erhalten Sie zum Beispiel den in-Operator als Funktion:

js
Reflect.has(Object, "assign"); // true

Eine bessere apply()-Funktion

Vor Reflect verwendeten Sie typischerweise die Methode Function.prototype.apply(), um eine Funktion mit einem bestimmten this-Wert und Argumenten, die als Array (oder ein Array-ähnliches Objekt) bereitgestellt werden, aufzurufen.

js
Function.prototype.apply.call(Math.floor, undefined, [1.75]);

Mit Reflect.apply wird dies weniger umständlich und verständlicher:

js
Reflect.apply(Math.floor, undefined, [1.75]);
// 1

Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111]);
// "hello"

Reflect.apply(RegExp.prototype.exec, /ab/, ["confabulation"]).index;
// 4

Reflect.apply("".charAt, "ponies", [3]);
// "i"

Überprüfen, ob die Eigenschaftsdefinition erfolgreich war

Mit Object.defineProperty, das ein Objekt zurückgibt, wenn es erfolgreich ist, oder andernfalls einen TypeError auslöst, würden Sie einen try...catch-Block verwenden, um Fehler beim Definieren einer Eigenschaft abzufangen. Da Reflect.defineProperty() einen booleschen Erfolgsstatus zurückgibt, können Sie hier einfach einen if...else-Block verwenden:

js
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}