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:
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
Die folgende Tabelle fasst die verfügbaren Traps, die Proxy
-Objekten zur Verfügung stehen, zusammen. Siehe die Referenzseiten für detaillierte Erklärungen und Beispiele.
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
.
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:
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.
Function.prototype.apply.call(Math.floor, undefined, [1.75]);
Mit Reflect.apply
wird dies weniger umständlich und leichter verständlich:
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:
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}