Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten.Erfahre mehr über dieses Experiment.
Proxy
Baseline Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since September 2016.
DasProxy-Objekt ermöglicht Ihnen, einen Proxy für ein anderes Objekt zu erstellen, der die grundlegenden Operationen für dieses Objekt abfangen und neu definieren kann.
In diesem Artikel
Beschreibung
DasProxy-Objekt ermöglicht Ihnen, ein Objekt zu erstellen, das anstelle des Originalobjekts verwendet werden kann, aber grundlegendeObject-Operationen wie das Lesen, Setzen und Definieren von Eigenschaften neu definieren kann. Proxy-Objekte werden häufig verwendet, um den Zugriff auf Eigenschaften zu protokollieren, Eingaben zu validieren, zu formatieren oder zu bereinigen und vieles mehr.
Sie erstellen einenProxy mit zwei Parametern:
target: das Originalobjekt, das Sie proxyen möchtenhandler: ein Objekt, das definiert, welche Operationen abgefangen und wie die abgefangenen Operationen neu definiert werden sollen
Zum Beispiel erstellt dieser Code einen Proxy für dastarget-Objekt.
const target = { message1: "hello", message2: "everyone",};const handler1 = {};const proxy1 = new Proxy(target, handler1);Da der Handler leer ist, verhält sich dieser Proxy genauso wie das ursprüngliche Ziel:
console.log(proxy1.message1); // helloconsole.log(proxy1.message2); // everyoneUm den Proxy anzupassen, definieren wir Funktionen im Handler-Objekt:
const target = { message1: "hello", message2: "everyone",};const handler2 = { get(target, prop, receiver) { return "world"; },};const proxy2 = new Proxy(target, handler2);Hier haben wir eine Implementierung desget() Handlers bereitgestellt, der Zugriffsversuche auf Eigenschaften im Ziel abfängt.
Handler-Funktionen werden manchmal alsTraps bezeichnet, vermutlich weil sie Aufrufe an das Zielobjekt abfangen. Der Trap im obigenhandler2 definiert alle Eigenschafts-Accessoren neu:
console.log(proxy2.message1); // worldconsole.log(proxy2.message2); // worldProxys werden oft mit demReflect-Objekt verwendet, das einige Methoden mit denselben Namen wie dieProxy-Traps bereitstellt. DieReflect-Methoden bieten die reflexiven Semantiken für das Aufrufen der entsprechendenobjektinternen Methoden. Zum Beispiel können wirReflect.get aufrufen, wenn wir das Verhalten des Objekts nicht neu definieren möchten:
const target = { message1: "hello", message2: "everyone",};const handler3 = { get(target, prop, receiver) { if (prop === "message2") { return "world"; } return Reflect.get(...arguments); },};const proxy3 = new Proxy(target, handler3);console.log(proxy3.message1); // helloconsole.log(proxy3.message2); // worldDieReflect-Methode interagiert immer noch mit dem Objekt durch objektinterne Methoden — sie „deproxifiziert“ den Proxy nicht, wenn sie auf einem Proxy aufgerufen wird. Wenn SieReflect-Methoden innerhalb eines Proxy-Traps verwenden und derReflect-Methodenaufruf erneut vom Trap abgefangen wird, kann es zu einer Endlosschleife kommen.
Terminologie
Die folgenden Begriffe werden verwendet, wenn über die Funktionalität von Proxys gesprochen wird.
- handler
Das Objekt, das als zweites Argument an den
Proxy-Konstruktor übergeben wird. Es enthält die Traps, die das Verhalten des Proxys definieren.- trap
Die Funktion, die das Verhalten für die entsprechendeobjektinterne Methode definiert. (Dies ist analog zum Konzept derTraps in Betriebssystemen.)
- target
Objekt, das der Proxy virtualisiert. Es wird oft als Speicher-Backend für den Proxy verwendet. Invarianten (Semantiken, die unverändert bleiben) in Bezug auf die Nichterweiterbarkeit des Objekts oder nicht konfigurierbare Eigenschaften werden gegen das Ziel überprüft.
- invariants
Semantiken, die unverändert bleiben, wenn benutzerdefinierte Operationen implementiert werden. Wenn Ihre Trap-Implementierung die Invarianten eines Handlers verletzt, wird ein
TypeErrorausgelöst.
Objektinterne Methoden
Objekte sind Sammlungen von Eigenschaften. Die Sprache bietet jedoch keine Mechanismen, um die im Objekt gespeicherten Datendirekt zu manipulieren — stattdessen definiert das Objekt einige interne Methoden, die spezifizieren, wie mit ihm interagiert werden kann. Zum Beispiel, wenn Sieobj.x lesen, erwarten Sie vielleicht, dass Folgendes passiert:
- Die
x-Eigenschaft wird imPrototypen-Verkettung gesucht, bis sie gefunden wird. - Wenn
xeine Dateneigenschaft ist, wird dasvalue-Attribut des Eigenschaftsdeskriptors zurückgegeben. - Wenn
xeine Accessor-Eigenschaft ist, wird der Getter aufgerufen und der Rückgabewert des Getters zurückgegeben.
Es gibt nichts Besonderes an diesem Prozess in der Sprache — es liegt daran, dass gewöhnliche Objekte standardmäßig eine[[Get]]-interne Methode haben, die mit diesem Verhalten definiert ist. Dieobj.x-Eigenschaftsabzugssyntax ruft einfach die[[Get]]-Methode des Objekts auf und das Objekt verwendet seine eigene interne Methodenumsetzung, um zu bestimmen, was zurückgegeben werden soll.
Ein anderes Beispiel:Arrays unterscheiden sich von normalen Objekten, da sie eine magischelength-Eigenschaft haben, die, wenn modifiziert, automatisch leere Slots zuordnet oder Elemente aus dem Array entfernt. Ebenso ändert das Hinzufügen von Array-Elementen automatisch dielength-Eigenschaft. Dies liegt daran, dass Arrays eine[[DefineOwnProperty]]-interne Methode haben, die weiß,length zu aktualisieren, wenn ein ganzzahliger Index geschrieben wird, oder die Array-Inhalte zu aktualisieren, wennlength geschrieben wird. Solche Objekte, deren interne Methoden unterschiedliche Implementierungen als gewöhnliche Objekte haben, werden alsexotische Objekte bezeichnet.Proxy ermöglicht es Entwicklern, ihre eigenen exotischen Objekte mit voller Kapazität zu definieren.
Alle Objekte haben die folgenden internen Methoden:
| Interne Methode | Entsprechender Trap |
|---|---|
[[GetPrototypeOf]] | getPrototypeOf() |
[[SetPrototypeOf]] | setPrototypeOf() |
[[IsExtensible]] | isExtensible() |
[[PreventExtensions]] | preventExtensions() |
[[GetOwnProperty]] | getOwnPropertyDescriptor() |
[[DefineOwnProperty]] | defineProperty() |
[[HasProperty]] | has() |
[[Get]] | get() |
[[Set]] | set() |
[[Delete]] | deleteProperty() |
[[OwnPropertyKeys]] | ownKeys() |
Funktionsobjekte haben auch die folgenden internen Methoden:
| Interne Methode | Entsprechender Trap |
|---|---|
[[Call]] | apply() |
[[Construct]] | construct() |
Es ist wichtig zu erkennen, dass alle Interaktionen mit einem Objekt letztendlich auf die Aufrufung einer dieser internen Methoden hinauslaufen und dass sie alle durch Proxys anpassbar sind. Das bedeutet, dass fast kein Verhalten (außer bestimmte kritische Invarianten) in der Sprache garantiert ist — alles wird durch das Objekt selbst definiert. Wenn Siedelete obj.x ausführen, gibt es keine Garantie dafür, dass"x" in obj danachfalse zurückgibt — es hängt von den Implementierungen des Objekts von[[Delete]] und[[HasProperty]] ab. Eindelete obj.x kann Dinge in die Konsole protokollieren, einen globalen Status ändern oder sogar eine neue Eigenschaft definieren, anstatt die bestehende zu löschen, obwohl diese Semantiken in Ihrem eigenen Code vermieden werden sollten.
Alle internen Methoden werden von der Sprache selbst aufgerufen und sind in JavaScript-Code nicht direkt zugänglich. DerReflect-Namensraum bietet Methoden, die wenig mehr tun, als die internen Methoden aufzurufen, außer einigen Eingabenormalisierungen und -validierungen. Auf jeder Trap-Seite listen wir mehrere typische Situationen auf, wann der Trap aufgerufen wird, aber diese internen Methoden werden ansehr vielen Stellen aufgerufen. Zum Beispiel lesen und schreiben Array-Methoden auf Array durch diese internen Methoden, sodass Methoden wiepush() auchget()- undset()-Traps aufrufen würden.
Die meisten internen Methoden sind in ihrem Zweck klar. Die einzigen beiden, die verwirrt werden könnten, sind[[Set]] und[[DefineOwnProperty]]. Für normale Objekte ruft erstere Setter auf; letztere nicht. (Und[[Set]] ruft[[DefineOwnProperty]] intern auf, wenn keine bestehende Eigenschaft vorhanden ist oder die Eigenschaft eine Dateneigenschaft ist.) Während Sie vielleicht wissen, dass dieobj.x = 1-Syntax[[Set]] verwendet undObject.defineProperty()[[DefineOwnProperty]] verwendet, ist es nicht sofort ersichtlich, welche Semantiken andere eingebaute Methoden und Syntaxen verwenden. Zum Beispiel verwendenKlassenfelder die[[DefineOwnProperty]]-Semantik, weshalb Setter, die in der Superklasse definiert sind, nicht aufgerufen werden, wenn ein Feld in der abgeleiteten Klasse deklariert wird.
Konstruktor
Proxy()Erstellt ein neues
Proxy-Objekt.
Hinweis:Es gibt keineProxy.prototype-Eigenschaft, daher habenProxy-Instanzen keine speziellen Eigenschaften oder Methoden.
Statische Methoden
Proxy.revocable()Erstellt ein widerrufbares
Proxy-Objekt.
Beispiele
>Einfaches Beispiel
In diesem Beispiel wird die Zahl37 als Standardwert zurückgegeben, wenn der Eigenschaftsname nicht im Objekt vorhanden ist. Es wird derget() Handler verwendet.
const handler = { get(obj, prop) { return prop in obj ? obj[prop] : 37; },};const p = new Proxy({}, handler);p.a = 1;p.b = undefined;console.log(p.a, p.b); // 1, undefinedconsole.log("c" in p, p.c); // false, 37No-op-Weiterleitungsproxy
In diesem Beispiel verwenden wir ein natives JavaScript-Objekt, an das unser Proxy alle Operationen weiterleiten wird, die darauf angewendet werden.
const target = {};const p = new Proxy(target, {});p.a = 37; // Operation forwarded to the targetconsole.log(target.a); // 37 (The operation has been properly forwarded!)Beachten Sie, dass dieses "No-op" für einfache JavaScript-Objekte funktioniert, aber nicht für native Objekte wie DOM-Elemente,Map-Objekte oder alles, was interne Slots hat. Siehekeine Weiterleitung privater Felder für weitere Informationen.
Keine Weiterleitung privater Felder
Ein Proxy ist weiterhin ein anderes Objekt mit einer anderen Identität — er ist einProxy, der zwischen dem umhüllten Objekt und der Außenwelt operiert. Daher hat der Proxy keinen direkten Zugriff auf dieprivaten Elemente des Originalobjekts.
class Secret { #secret; constructor(secret) { this.#secret = secret; } get secret() { return this.#secret.replace(/\d+/, "[REDACTED]"); }}const secret = new Secret("123456");console.log(secret.secret); // [REDACTED]// Looks like a no-op forwarding...const proxy = new Proxy(secret, {});console.log(proxy.secret); // TypeError: Cannot read private member #secret from an object whose class did not declare itDies liegt daran, dass, wenn derget-Trap des Proxys aufgerufen wird,this-Wert derproxy anstelle des ursprünglichensecret ist, sodass#secret nicht zugänglich ist. Um dies zu beheben, verwenden Sie das ursprünglichesecret alsthis:
const proxy = new Proxy(secret, { get(target, prop, receiver) { // By default, it looks like Reflect.get(target, prop, receiver) // which has a different value of `this` return target[prop]; },});console.log(proxy.secret);Für Methoden bedeutet dies, dass Sie denthis-Wert der Methode auf das ursprüngliche Objekt ebenfalls umleiten müssen:
class Secret { #x = 1; x() { return this.#x; }}const secret = new Secret();const proxy = new Proxy(secret, { get(target, prop, receiver) { const value = target[prop]; if (value instanceof Function) { return function (...args) { return value.apply(this === receiver ? target : this, args); }; } return value; },});console.log(proxy.x());Einige native JavaScript-Objekte haben Eigenschaften, die alsinterne Slots bezeichnet werden und die vom JavaScript-Code nicht zugänglich sind. Zum Beispiel habenMap-Objekte einen internen Slot namens[[MapData]], der die Schlüssel-Wert-Paare der Karte speichert. Daher können Sie nicht trivial einen Weiterleitungsproxy für eine Karte erstellen:
const proxy = new Proxy(new Map(), {});console.log(proxy.size); // TypeError: get size method called on incompatible ProxySie müssen den oben illustrierten „this-Wiederherstellungs“-Proxy verwenden, um dies zu umgehen.
Validierung
Mit einemProxy können Sie leicht den übergebenen Wert für ein Objekt validieren. Dieses Beispiel verwendet denset() Handler.
const validator = { set(obj, prop, value) { if (prop === "age") { if (!Number.isInteger(value)) { throw new TypeError("The age is not an integer"); } if (value > 200) { throw new RangeError("The age seems invalid"); } } // The default behavior to store the value obj[prop] = value; // Indicate success return true; },};const person = new Proxy({}, validator);person.age = 100;console.log(person.age); // 100person.age = "young"; // Throws an exceptionperson.age = 300; // Throws an exceptionManipulation von DOM-Knoten
In diesem Beispiel verwenden wirProxy, um ein Attribut von zwei verschiedenen Elementen umzuschalten: Wenn wir das Attribut an einem Element setzen, wird das Attribut am anderen automatisch gelöscht.
Wir erstellen einview-Objekt, das ein Proxy für ein Objekt mit einerselected-Eigenschaft ist. Der Proxy-Handler definiert denset() Handler.
Wenn wir ein HTML-Elementview.selected zuweisen, wird das'aria-selected'-Attribut des Elements auftrue gesetzt. Wenn wir dann ein anderes Elementview.selected zuweisen, wird das'aria-selected'-Attribut dieses Elements auftrue gesetzt und das vorherige Element's'aria-selected'-Attribut wird automatisch auffalse gesetzt.
const view = new Proxy( { selected: null, }, { set(obj, prop, newVal) { const oldVal = obj[prop]; if (prop === "selected") { if (oldVal) { oldVal.setAttribute("aria-selected", "false"); } if (newVal) { newVal.setAttribute("aria-selected", "true"); } } // The default behavior to store the value obj[prop] = newVal; // Indicate success return true; }, },);const item1 = document.getElementById("item-1");const item2 = document.getElementById("item-2");// select item1:view.selected = item1;console.log(`item1: ${item1.getAttribute("aria-selected")}`);// item1: true// selecting item2 de-selects item1:view.selected = item2;console.log(`item1: ${item1.getAttribute("aria-selected")}`);// item1: falseconsole.log(`item2: ${item2.getAttribute("aria-selected")}`);// item2: trueWertkorrektur und eine zusätzliche Eigenschaft
Dasproducts-Proxy-Objekt bewertet den übergebenen Wert und konvertiert ihn bei Bedarf in ein Array. Das Objekt unterstützt auch eine zusätzliche Eigenschaft namenslatestBrowser sowohl als Getter als auch als Setter.
const products = new Proxy( { browsers: ["Firefox", "Chrome"], }, { get(obj, prop) { // An extra property if (prop === "latestBrowser") { return obj.browsers[obj.browsers.length - 1]; } // The default behavior to return the value return obj[prop]; }, set(obj, prop, value) { // An extra property if (prop === "latestBrowser") { obj.browsers.push(value); return true; } // Convert the value if it is not an array if (typeof value === "string") { value = [value]; } // The default behavior to store the value obj[prop] = value; // Indicate success return true; }, },);console.log(products.browsers);// ['Firefox', 'Chrome']products.browsers = "Safari";// pass a string (by mistake)console.log(products.browsers);// ['Safari'] <- no problem, the value is an arrayproducts.latestBrowser = "Edge";console.log(products.browsers);// ['Safari', 'Edge']console.log(products.latestBrowser);// 'Edge'Spezifikationen
| Specification |
|---|
| ECMAScript® 2026 Language Specification> # sec-proxy-objects> |
Browser-Kompatibilität
Siehe auch
- Proxies are awesome Präsentation von Brendan Eich auf der JSConf (2014)