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 TypeError erzeugt.

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
  • Die getPrototypeOf Methode muss ein Objekt or null zurückgeben.
  • Wenn target nicht erweiterbar ist, muss die Object.getPrototypeOf(proxy) Methode das gleiche zurückgeben wie Object.getPrototypeOf(target).
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()
  • getOwnPropertyDescriptor muss ein Objekt oder undefined zurückgeben.
  • Eine Eigenschaft kann nicht als nichtexistent erkannt werden, wenn sie als nicht konfigurierbare Eigenschaft des Zielobjektes existiert.
  • Eine Eigenschaft kann nicht als nichtexistent erkannt werden, wenn sie als Eigenschaft des Zielobjektes existiert und das Zielobjekt nicht erweiterbar ist.
  • Eine Eigenschaft kann nicht als existent erkannt werden, wenn sie nicht als Eigenschaft des Zielobjektes existiert und das Zielobjekt nicht erweiterbar ist.
  • Eine Eigenschaft kann nicht als nicht konfigurierbar erkannt werden, wenn sie nicht als Eigenschaft des Zielobjektes existiert oder wenn sie als konfigurierbare Eigenschaft des Zielobjekt existiert.
  • Das Ergebnis von Object.getOwnPropertyDescriptor(target) kann dem Zielobjekt mit Object.defineProperty übergeben werden ohne, dass ein Fehler erzeugt wird.
handler.defineProperty() Object.defineProperty()
Reflect.defineProperty()
  • Eine Eigenschaft kann nicht hinzugefügt werden, wenn das Zielobjekt nicht erweiterbar ist.
  • Eine Eigenschaft kann nicht hinzugefügt werden oder zu nicht konfigurierbar geändert werden, wenn es nicht als nicht konfigurierbares Eigenschaft im Zielobjekt existiert.
  • Eine Eigenschaft darf nicht nicht konfigurierbar sein, wenn die zugehörige konfigurierbare Eigenschaft im Zielobjekt existiert.
  • Wenn eine Eigenschaft eine zugehörige Eigenschaft im Zielobjekt hat, so wird Object.defineProperty(target, prop, descriptor) keinen Fehler erzeugen.
  • Im Strict Mode, wird ein falscher Rückgabewert des defineProperty Handler einen TypeError Erzeugen.
handler.has() Eigenschaftsabfrage: foo in proxy
Vererbte Eigenschaftsabfrage: foo in Object.create(proxy)
Reflect.has()
  • Eine Eigenschaft kan nicht als nichtexistent erkannt werden, wenn sie als nicht konfigurierbare Eigenschaft im Zielobjekt existiert.
  • Eine Eigenschaft kan nicht als nichtexistent erkannt werden, wenn sie als Eigenschaft im Zielobjekt existiert und das Zielobjekt nicht erweiterbar ist.
handler.get() Eigenschaftszugriff: proxy[foo]and proxy.bar
Vererbter Eigenschaftszugriff: Object.create(proxy)[foo]
Reflect.get()
  • Der Wert, der für eine Eigenschaft zurückgegeben wird, muss der gleiche sein wie der in der zugehörigen Eigenschaft des Zielobjekts, wenn die Eigenschaft im Zielobjekt nicht überschreibbar und nicht konfigurierbar ist.
  • Der Wert, der für eine Eigenschaft zurückgegeben wird, muss undefined sein, wenn die zugehörige Eigenschaft im Zielobjekt einen nicht konfigurierbare Zugriffseigenschaft hat, dessen [[Get]] Attribut undefined ist.
handler.set() Eigenschaftszuweisung: proxy[foo] = bar and proxy.foo = bar
Vererbte Eigenschaftszuweisung: Object.create(proxy)[foo] = bar
Reflect.set()
  • Der Wert kann nicht zu einem geändert werden, der anders als dem Wert im Zielobjekt ist, wenn die zugehörige Eigenschaft im Zielobjekt eine nicht überschreibbare, nicht konfigurierbare Dateneigenschaft ist.
  • Der Wert der Eigenschaft kann nicht geändert werden, wenn die zugehörige Eigenschaft im Zielobjekt nicht konfigurierbar ist und das [[Set]] Attribut den Wert undefined hat.
  • Im Strict Mode, wird ein falscher Rückgabewert des set Handlers einen TypeError erzeugen.
handler.deleteProperty() Eigenschaft löschen: delete proxy[foo] und delete proxy.foo
Reflect.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()
  • Das Ergebnis von ownKeys ist eine Liste.
  • Der Typ jedes Elements in der Ergebnisliste ist entweder String oder Symbol.
  • Die Ergebnisliste muss alle Schlüssel von nicht konfigurierbaren Eigenschaften des Zielobjektes enthalten.
  • Wenn das Zielobjekt nicht erweiterbar ist, muss die Ergebnisliste alle Schlüssel des Zielobjektes enthalten und keine anderen Werte.
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
}

Schlagwörter des Dokuments und Mitwirkende

Mitwirkende an dieser Seite: schlagi123
Zuletzt aktualisiert von: schlagi123,