Beginnend mit ECMAScript 2015 hat JavaScript Unterstützung für Proxy and Reflect Objekte erhalten, welche das Abfangen und Definieren von benutzerdefinierten Verhaltens für grundlegenden Sprachoperation erlaubt (z. B. Eigenschaftensuche, Zuweisung, Aufzählung, Funktionsaufruf usw.). Mit der Hilfe dieser beiden Objekte ist es möglich auf der Metaebene von JavaScript zu programmieren.
Proxies
Eingeführt in ECMAScript 6 erlaubt das Proxy Objekt das Abfangen und Definieren von benutzerdefinierten Verhaltens für bestimmte Operationen. Zum Beispiel um die Eigenschaft eines Objektes zu erhalten:
var handler = {
get: function(target, name) {
return name in target ? target[name] : 42;
}
};
var p = new Proxy({}, handler);
p.a = 1;
console.log(p.a, p.b); // 1, 42
Das Proxy Objekt definiert einen Ziel (target) (hier ein leeres Objekt) und einen handler (Verhaltens) Objekt in dem ein get Trap implementiert ist. In diesem Beispiel wird kein undefined zurückgegeben, wenn Eigenschaften nicht definiert sind. Stattdessen wird die Zahl 42 zurückgegeben.
Weitere Beispiele sind auf der Proxy Referenzseite verfügbar.
Terminologie
Die folgenden Terme werden im Zusammenhang mit der Funktionalität von Proxies verwendet.
- Handler
- Platzhalterobjekt, welches Traps enthält.
- Traps
- Die Methoden, die Zugriff auf Eigenschaften unterstützen. Diese sind analog zu Traps in Betriebssystemen.
- Ziel
- Objekt, welches vom Proxy virtualisiert wird. Es wird häufig als Speicher-Backend für den Proxy benutzt. Invarianten (Semantik, die unverändert bleiben) bezüglich nicht erweiterbarer Objekteigenschaften oder nicht konfigurierbarer Eigenschaften werden gegen das Ziel verifiziert.
- Invarianten
- Semantiken, die bei der Implementierung von benutzerdefinierten Operationen unverändert bleiben, werden als Invarianten bezeichnet. Wenn Sie gegen die Invarianten eines Handlers verstoßen, wird ein
TypeErrorerzeugt.
Handlers und Traps
Die Folgende Tabelle fasst die verfügbaren Traps von Proxy Objekten zusammen. Siehe auf der Referenzseite für detailliertere Erklärungen und Beispiele.
| Handler / Trap | Interceptions | Invarianten |
|---|---|---|
handler.getPrototypeOf() |
Object.getPrototypeOf()Reflect.getPrototypeOf()__proto__Object.prototype.isPrototypeOf()instanceof |
|
handler.setPrototypeOf() |
Object.setPrototypeOf()Reflect.setPrototypeOf() |
Wenn target nicht erweiterbar ist, muss der prototype Parameter der gleiche Wert sein wie Object.getPrototypeOf(target). |
handler.isExtensible() |
Object.isExtensible()Reflect.isExtensible() |
Object.isExtensible(proxy) muss den gleichen Wert wie Object.isExtensible(target) zurückgeben. |
handler.preventExtensions() |
Object.preventExtensions()Reflect.preventExtensions() |
Object.preventExtensions(proxy) gibt nur true zurück, wenn Object.isExtensible(proxy) false ist. |
handler.getOwnPropertyDescriptor() |
Object.getOwnPropertyDescriptor()Reflect.getOwnPropertyDescriptor() |
|
handler.defineProperty() |
Object.defineProperty()Reflect.defineProperty() |
|
handler.has() |
Eigenschaftsabfrage: foo in proxyVererbte Eigenschaftsabfrage: foo in Object.create(proxy)Reflect.has() |
|
handler.get() |
Eigenschaftszugriff: proxy[foo]and proxy.barVererbter Eigenschaftszugriff: Object.create(proxy)[foo]Reflect.get() |
|
handler.set() |
Eigenschaftszuweisung: proxy[foo] = bar and proxy.foo = barVererbte Eigenschaftszuweisung: Object.create(proxy)[foo] = barReflect.set() |
|
handler.deleteProperty() |
Eigenschaft löschen: delete proxy[foo] und delete proxy.fooReflect.deleteProperty() |
Eine Eigenschaft kann nicht gelöscht werden, Wenn sie als nicht konfigurierbare Eigenschaft im Zielobjekt existiert. |
handler.enumerate() |
Eigenschaft aufzählen (enumeration) / for...in: for (var name in proxy) {...}Reflect.enumerate() |
Die enumerate Methode muss ein Objekt zurückgeben. |
handler.ownKeys() |
Object.getOwnPropertyNames()Object.getOwnPropertySymbols()Object.keys()Reflect.ownKeys() |
|
handler.apply() |
proxy(..args)Function.prototype.apply() and Function.prototype.call()Reflect.apply() |
Es gibt keine Invarianten für die handler.apply Methode. |
handler.construct() |
new proxy(...args)Reflect.construct() |
Das Ergebnis muss ein Object sein. |
Wiederrufbarer Proxy
Die Proxy.revocable() Methode wird benutzt, um ein wiederrufbares Proxy Objekt zu erstellen. Das bedeutet, dass der Proxy mit der Funktion revoke wiederrufen werden kann und der Proxy ausgeschaltet wird. Danach wird jede Operation auf dem Proxy zu einem TypeError führen.
var revocable = Proxy.revocable({}, {
get: function(target, name) {
return '[[' + name + ']]';
}
});
var proxy = revocable.proxy;
console.log(proxy.foo); // "[[foo]]"
revocable.revoke();
console.log(proxy.foo); // TypeError is thrown
proxy.foo = 1; // TypeError again
delete proxy.foo; // still TypeError
typeof proxy; // "object", typeof doesn't trigger any trap
Reflection
Reflect ist ein Standardobjekt welches Methoden unterstützt, welche das Abfragen von JavaScript Operationen erlauben. Die Methoden sind die gleichen wie die eines Proxy Handlers. Reflect ist kein Funktionsobjekt.
Reflect hilft beim Weiterleiten von Standardoperationen des Handlers zu dem Zielobjekt.
Mit bekommt man Reflect.has() zum Beispiel den in Operator als Funktion:
Reflect.has(Object, 'assign'); // true
Eine bessere apply Funktion
In ES5 wird typischerweise die Function.prototype.apply() Methode genutzt, um eine Funktion mit einem gegebenen this Wert und arguments als Array (oder ein Array-ähnliches Objekt) benutzt.
Function.prototype.apply.call(Math.floor, undefined, [1.75]);
Mit Reflect.apply wird dieses weniger Langatmig 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"
Prüfen ob Eigenschaftsdefinitionen erfolgreich waren
Mit Object.defineProperty, welche ein Objekt zurück gibt, wenn es erfolgreich war, oder andernfalls ein TypeError erzeugt, muss man ein try...catch Block benutzen, um einen Fehler bei der Definition einer Eigenschaft abzufangen. Weil Reflect.defineProperty einen Boolean als Status zurück gibt, kann man einfach einen if...else Block benutzen:
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}