Metaprogrammierung
Die Objekte Proxy und Reflect ermöglichen es Ihnen, fundamentale Sprachoperationen (z. B. Eigenschaftenabfrage, Zuweisung, Aufzählung, Funktionsaufruf usw.) abzufangen und benutzerdefinierte Verhaltensweisen zu definieren. Mit Hilfe dieser beiden Objekte sind Sie in der Lage, auf der Metaebene von JavaScript zu programmieren.
Proxies
Proxy-Objekte erlauben es Ihnen, bestimmte Operationen abzufangen und benutzerdefinierte Verhaltensweisen zu implementieren.
Zum Beispiel beim Abrufen einer Eigenschaft eines Objekts:
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-Falle (trap) implementiert ist. Hierbei wird ein proxied Objekt nicht undefined zurückgeben, wenn undefinierte Eigenschaften abgefragt werden, sondern stattdessen die Zahl 42.
Zusätzliche Beispiele sind auf der Proxy-Referenzseite verfügbar.
Terminologie
Die folgenden Begriffe werden verwendet, wenn über die Funktionalität von Proxies gesprochen wird.
- handler
-
Platzhalterobjekt, das Fallen enthält.
- Fallen (traps)
-
Die Methoden, die den Zugriff auf Eigenschaften bereitstellen. (Dies ist analog zum Konzept der Fallen in Betriebssystemen.)
- Ziel (target)
-
Objekt, das vom Proxy virtualisiert wird. Es wird häufig als Speicherungshintergrund für den Proxy verwendet. Invarianten (Semantiken, die unverändert bleiben) in Bezug auf die Nichterweiterbarkeit von Objekten oder nicht konfigurierbare Eigenschaften werden gegen das Ziel verifiziert.
- Invarianten
-
Semantiken, die bei der Implementierung benutzerdefinierter Operationen unverändert bleiben, werden als Invarianten bezeichnet. Wenn Sie die Invarianten eines Handlers verletzen, wird ein
TypeErrorausgelöst.
Handler und Fallen
Die folgende Tabelle fasst die verfügbaren Fallen zusammen, die für Proxy-Objekte verfügbar sind. Siehe die Referenzseiten für detaillierte Erklärungen und Beispiele.
Widerrufbarer Proxy
Die Methode Proxy.revocable() wird verwendet, um ein widerrufbares Proxy-Objekt zu erstellen. Das bedeutet, dass der Proxy über die Funktion revoke widerrufen werden kann und der Proxy abgeschaltet wird.
Danach führt jede Operation am Proxy zu einem TypeError.
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 bietet. 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() zum Beispiel erhalten Sie den in-Operator als Funktion:
Reflect.has(Object, "assign"); // true
Eine bessere apply()-Funktion
Vor Reflect haben Sie typischerweise die Methode Function.prototype.apply() verwendet, um eine Funktion mit einem gegebenen this-Wert und Arguments, die als Array (oder ein Array-ähnliches Objekt) bereitgestellt werden, aufzurufen.
Function.prototype.apply.call(Math.floor, undefined, [1.75]);
Mit Reflect.apply wird dies weniger wortreich und einfacher zu verstehen:
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 war, oder ansonsten einen TypeError auslöst, würden Sie einen try...catch-Block verwenden, um einen beliebigen Fehler abzufangen, der beim Definieren einer Eigenschaft aufgetreten ist. Da Reflect.defineProperty() einen Boolean-Erfolgsstatus zurückgibt, können Sie hier einfach einen if...else-Block verwenden:
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}