Metaprogrammierung

Die Objekte Proxy und Reflect ermöglichen es Ihnen, grundlegende Sprachoperationen (z. B. Eigenschaftssuche, Zuweisung, Aufzählung, Funktionsaufruf usw.) abzufangen und benutzerdefiniertes Verhalten zu definieren. Mit Hilfe dieser beiden Objekte können Sie auf der Metaebene 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 ein get-Trap implementiert ist. Hier wird ein proxied Objekt nicht undefined zurückgeben, wenn nicht definierte Eigenschaften abgerufen werden, sondern stattdessen die Zahl 42.

Weitere Beispiele sind auf der Proxy-Referenzseite verfügbar.

Terminologie

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

handler

Platzhalterobjekt, das Traps enthält.

Traps

Die Methoden, die den Zugriff auf Eigenschaften bereitstellen. (Dies ist analog zum Konzept der Traps in Betriebssystemen.)

Target

Objekt, welches der Proxy virtualisiert. Es wird oft als Speicher-Backend für den Proxy verwendet. Invarianten (Semantik, die unverändert bleibt) in Bezug auf die Nichterweiterbarkeit oder nicht konfigurierbare Eigenschaften von Objekten werden gegen das Target überprüft.

Invarianten

Semantik, die unverändert bleibt, wenn benutzerdefinierte Operationen implementiert werden, werden als Invarianten bezeichnet. Wenn Sie die Invarianten eines Handlers verletzen, wird ein TypeError ausgelöst.

Handler und Traps

Widerrufbarer Proxy

Die Proxy.revocable()-Methode wird verwendet, um ein widerrufbares Proxy-Objekt zu erstellen. Das 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 dieselben wie die des Proxy-Handlers.

Reflect ist kein Funktionsobjekt.

Reflect hilft dabei, Standardoperationen vom Handler an das target weiterzuleiten.

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

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

Eine bessere apply()-Funktion

Vor Reflect verwenden Sie typischerweise die Methode Function.prototype.apply(), um eine Funktion mit einem bestimmten this-Wert und übergebenen arguments als Array (oder einem array-ähnlichen Objekt) aufzurufen.

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

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

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üfung, ob die Eigenschaftsdefinition erfolgreich war

Mit Object.defineProperty, die ein Objekt zurückgibt, wenn sie erfolgreich ist, oder einen TypeError auslöst, wenn nicht, würden Sie einen try...catch-Block verwenden, um einen Fehler abzufangen, der bei der Definition einer Eigenschaft auftritt. Da Reflect.defineProperty() einen Boolean-Erfolgsstatus zurückgibt, können Sie einfach einen if...else-Block verwenden:

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