Kodokon kodokon.com

Proxy, Reflect und Metaprogrammierung

Fange die grundlegenden Operationen der Sprache mit Proxy-Traps ab und delegiere das Standardverhalten sauber mit Reflect.

10 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

Ein Proxy umhüllt ein Zielobjekt und fängt die internen Operationen der Sprache über dreizehn Traps ab: get, set, has (der in-Operator), deleteProperty, ownKeys, apply, construct und so weiter. Das Reflect-Objekt stellt genau dieselben Operationen als Funktionen bereit, mit denselben Signaturen wie die Traps. Es ist die Grundlage der Reaktivität von Vue 3, von dynamischen Mocks und von APIs im ORM-Stil.

JAVASCRIPT
const target = { name: "Ada" };

const traced = new Proxy(target, {
  get(obj, prop, receiver) {
    console.log("get " + String(prop));
    return Reflect.get(obj, prop, receiver);
  }
});

traced.name; // get name
Eine minimale get-Trap, die an Reflect delegiert.

Warum Reflect.get(obj, prop, receiver) statt obj[prop]? Wegen des Receivers. Ist die Eigenschaft ein Accessor (ein Getter), ruft Reflect.get ihn mit this auf, das auf den Receiver zeigt - den Proxy oder ein davon erbendes Objekt -, während obj[prop] this auf das rohe Ziel setzen würde. Ohne den Receiver brechen Getter, die this lesen, sobald ein Objekt vom Proxy erbt.

JAVASCRIPT
const base = {
  get label() {
    return this.name;
  }
};

const proxy = new Proxy(base, {
  get(target, prop, receiver) {
    return Reflect.get(target, prop, receiver);
  }
});

const child = Object.create(proxy);
child.name = "Ada";
console.log(child.label); // "Ada" thanks to the receiver
Der Receiver bewahrt this in geerbten Gettern.

Die ernsthaften Anwendungsfälle: Validierung beim Schreiben, virtuelle Eigenschaften, die spontan berechnet werden, Logging, Zugriffskontrolle (das Verbergen von Schlüsseln mit _-Präfix über has und ownKeys) oder Objekte mit Standardverhalten. Beachte, dass Reflect.set einen Boolean zurückgibt, statt im Strict Mode zu werfen: Deine Trap muss diesen Rückgabewert weiterreichen.

JAVASCRIPT
function validated(target) {
  return new Proxy(target, {
    set(obj, prop, value, receiver) {
      if (prop === "age" && !Number.isInteger(value)) {
        throw new TypeError("age must be an integer");
      }
      return Reflect.set(obj, prop, value, receiver);
    }
  });
}

const user = validated({ age: 30 });
user.age = 31;       // ok
// user.age = "old"; // TypeError
Transparente Validierung beim Schreiben.

Wissenscheck

Stelle sicher, dass du die wichtigsten Punkte dieser Lektion behalten hast.

  1. Warum in einer get-Trap Reflect.get(target, prop, receiver) gegenüber target[prop] bevorzugen?
    • Es ist schneller, weil Reflect den Prototyp umgeht
    • Damit Getter mit dem richtigen this (dem Receiver) laufen statt mit dem rohen Ziel
    • Weil target[prop] die Trap erneut auslösen und eine Endlosrekursion verursachen würde
    • Die beiden Formen sind streng gleichwertig
  2. Welche Trap fängt den Ausdruck "id" in obj ab?
    • get
    • has
    • ownKeys
    • getOwnPropertyDescriptor
  3. Eine get-Trap gibt 42 für eine Ziel-Eigenschaft zurück, deren Wert 1 ist und die als nicht-konfigurierbar und nicht-schreibbar deklariert ist. Was passiert?
    • Der Proxy gibt 42 zurück, die Trap hat immer Vorrang
    • Die Engine gibt still 1 zurück
    • Die Engine wirft einen TypeError wegen einer Invariantenverletzung
    • Das Verhalten hängt von der JavaScript-Engine ab