Meta programming
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 (en-US)
- 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
TypeError
erzeugt.
Handlers und Traps
Die Folgende Tabelle fasst die verfügbaren Traps von Proxy
Objekten zusammen. Siehe auf der Referenzseite (en-US) für detailliertere Erklärungen und Beispiele.
Handler / Trap | Interceptions | Invarianten |
---|---|---|
handler.getPrototypeOf() (en-US) |
Object.getPrototypeOf() Reflect.getPrototypeOf() (en-US)__proto__ Object.prototype.isPrototypeOf() (en-US)instanceof |
|
handler.setPrototypeOf() (en-US) |
Object.setPrototypeOf() (en-US)Reflect.setPrototypeOf() (en-US) |
Wenn target nicht erweiterbar ist, muss der prototype Parameter der gleiche Wert sein wie Object.getPrototypeOf(target) . |
handler.isExtensible() (en-US) |
Object.isExtensible() Reflect.isExtensible() (en-US) |
Object.isExtensible(proxy) muss den gleichen Wert wie Object.isExtensible(target) zurückgeben. |
handler.preventExtensions() (en-US) |
Object.preventExtensions() (en-US)Reflect.preventExtensions() (en-US) |
Object.preventExtensions(proxy) gibt nur true zurück, wenn Object.isExtensible(proxy) false ist. |
handler.getOwnPropertyDescriptor() (en-US) |
Object.getOwnPropertyDescriptor() (en-US)Reflect.getOwnPropertyDescriptor() (en-US) |
|
handler.defineProperty() (en-US) |
Object.defineProperty() (en-US)Reflect.defineProperty() (en-US) |
|
handler.has() (en-US) |
Eigenschaftsabfrage: foo in proxy Vererbte Eigenschaftsabfrage: foo in Object.create(proxy) Reflect.has() (en-US) |
|
handler.get() (en-US) |
Eigenschaftszugriff: proxy[foo] and proxy.bar Vererbter Eigenschaftszugriff: Object.create(proxy)[foo] Reflect.get() (en-US) |
|
handler.set() (en-US) |
Eigenschaftszuweisung: proxy[foo] = bar and proxy.foo = bar Vererbte Eigenschaftszuweisung: Object.create(proxy)[foo] = bar Reflect.set() (en-US) |
|
handler.deleteProperty() (en-US) |
Eigenschaft löschen: delete proxy[foo] und delete proxy.foo Reflect.deleteProperty() (en-US) |
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() (en-US) |
Object.getOwnPropertyNames() Object.getOwnPropertySymbols() (en-US)Object.keys() Reflect.ownKeys() (en-US) |
|
handler.apply() (en-US) |
proxy(..args) Function.prototype.apply() and Function.prototype.call() Reflect.apply() |
Es gibt keine Invarianten für die handler.apply Methode. |
handler.construct() (en-US) |
new proxy(...args) Reflect.construct() |
Das Ergebnis muss ein Object sein. |
Widerrufbarer Proxy
Die Proxy.revocable()
(en-US) Methode wird benutzt, um ein widerrufbares Proxy
Objekt zu erstellen. Das bedeutet, dass der Proxy mit der Funktion revoke
widerrufen 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 (en-US). Reflect
ist kein Funktionsobjekt.
Reflect
hilft beim Weiterleiten von Standardoperationen des Handlers zu dem Zielobjekt.
Mit bekommt man Reflect.has()
(en-US) 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
(en-US), 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
(en-US) einen Boolean als Status zurück gibt, kann man einfach einen if...else
Block benutzen:
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}