Metaprogrammierung

Die Objekte Proxy und Reflect ermöglichen es Ihnen, fundamentale Sprachoperationen (z.B. Eigenschaftslookup, Zuweisung, Aufzählung, Funktionsaufruf usw.) abzufangen und benutzerdefiniertes Verhalten zu definieren. Mit Hilfe dieser beiden Objekte können Sie auf der Meta-Ebene von JavaScript programmieren.

Proxies

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

Zum Beispiel, das Abrufen einer Eigenschaft auf 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 trap implementiert ist. Hier wird ein Objekt, das durch einen Proxy geschützt ist, nicht undefined zurückgeben, wenn auf undefinierte Eigenschaften zugegriffen wird, sondern stattdessen die Zahl 42.

Weitere 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 Traps enthält.

traps

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

target

Objekt, das der Proxy virtualisiert. Es wird oft als Speicherbackend für den Proxy verwendet. Invarianten (Semantiken, die unverändert bleiben) bezüglich der Nicht-Erweiterbarkeit von Objekten oder nicht-konfigurierbaren Eigenschaften werden gegen das Zielobjekt überprüft.

invariants

Semantiken, die unverändert bleiben, 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 Methode Proxy.revocable() wird verwendet, um ein widerrufbares Proxy-Objekt zu erstellen. Dies bedeutet, dass der Proxy über die Funktion revoke widerrufen und der Proxy abgeschaltet werden kann.

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 von Standardoperationen vom Handler zum 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 verwenden Sie typischerweise die Methode Function.prototype.apply(), um eine Funktion mit einem gegebenen this-Wert und arguments, die als Array (oder als 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 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ü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 einen etwaigen Fehler abzufangen, der bei der Definition einer Eigenschaft aufgetreten ist. 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
}