Start · Sprachen · JavaScript · Referenz · handler.apply()

handler.apply()

Methode

Proxy-Falle für die interne Methode <code>[[Call]]</code>: fängt Funktionsaufrufe ab, auch über <code>call()</code>, <code>apply()</code> und <code>Reflect.apply()</code>.

seit JavaScript ES2015 Kategorie: method-property

Signatur

new Proxy(target, { apply(target, thisArg, argumentsList) { … } })

Beschreibung

Die Falle handler.apply() wird ausgelöst, sobald ein Proxy um eine Funktion herum aufgerufen wird. Dazu gehören der normale Aufruf proxy(...args), Function.prototype.call(), Function.prototype.apply() und Reflect.apply(). So lassen sich Funktionsaufrufe abfangen, protokollieren, prüfen oder verändern, ohne die ursprüngliche Funktion anzufassen.

Typische Einsatzgebiete sind Logging und Tracing, Prüfung der Argumente, Memoisierung (Zwischenspeichern von Ergebnissen), Zeitmessung und das Nachrüsten von Zugriffskontrollen. Innerhalb der Falle leitet man den Aufruf meist mit Reflect.apply(target, thisArg, argumentsList) an die Originalfunktion weiter.

Wichtig: Die Falle funktioniert nur, wenn das target selbst aufrufbar ist, also eine Funktion. Ein Proxy um ein normales Objekt wird durch eine apply-Falle nicht aufrufbar. Aufrufe mit new laufen nicht über apply, sondern über die Falle construct.

Parameter

Name Typ Default Beschreibung
$target Pflicht Function Die Zielfunktion, um die der Proxy liegt.
$thisArg Pflicht any Der this-Wert des Aufrufs. Bei einem einfachen Aufruf proxy() ist er undefined, bei obj.proxy() ist es obj.
$argumentsList Pflicht Array Ein echtes Array mit den übergebenen Argumenten.

Rückgabewert

Typ
any
Beschreibung
Beliebiger Wert. Er wird zum Ergebnis des Funktionsaufrufs auf dem Proxy.

Beispiele

Funktionsaufrufe protokollieren

const add = (a, b) => a + b;

const loggedAdd = new Proxy(add, {
  apply(target, thisArg, args) {
    console.log(`Aufruf ${target.name}(${args.join(', ')})`);
    const result = Reflect.apply(target, thisArg, args);
    console.log(`Ergebnis: ${result}`);
    return result;
  }
});

loggedAdd(2, 3);
loggedAdd.call(null, 10, 5);
Aufruf add(2, 3) Ergebnis: 5 Aufruf add(10, 5) Ergebnis: 15

Argumente prüfen

const setVolume = (level) => `Lautstärke: ${level}`;

const safeSetVolume = new Proxy(setVolume, {
  apply(target, thisArg, [level]) {
    if (typeof level !== 'number' || level < 0 || level > 100) {
      throw new RangeError(`Ungültiger Wert: ${level}`);
    }
    return Reflect.apply(target, thisArg, [level]);
  }
});

console.log(safeSetVolume(42));
try {
  safeSetVolume(150);
} catch (err) {
  console.log(err.message);
}
Lautstärke: 42 Ungültiger Wert: 150

Memoisierung einer teuren Berechnung

const memoize = (fn) => {
  const cache = new Map();
  return new Proxy(fn, {
    apply(target, thisArg, args) {
      const key = JSON.stringify(args);
      if (cache.has(key)) return cache.get(key);
      const value = Reflect.apply(target, thisArg, args);
      cache.set(key, value);
      return value;
    }
  });
};

const slowSquare = (n) => {
  console.log(`berechne ${n}²`);
  return n * n;
};

const fastSquare = memoize(slowSquare);
console.log(fastSquare(9));
console.log(fastSquare(9));
berechne 9² 81 81

this-Wert bei Methodenaufrufen

const user = {
  name: 'Ada',
  greet: new Proxy(function (greeting) {
    return `${greeting}, ${this.name}!`;
  }, {
    apply(target, thisArg, args) {
      console.log('this ist user:', thisArg === user);
      return Reflect.apply(target, thisArg, args);
    }
  })
};

console.log(user.greet('Hallo'));
this ist user: true Hallo, Ada!

// Wichtig · Fallstricke

  • Ziel muss eine Funktion sein: Ist target nicht aufrufbar, wirft proxy() einen TypeError, egal ob eine apply-Falle vorhanden ist.
  • new läuft nicht über apply: Für new proxy() ist die Falle construct zuständig.
  • Leite mit Reflect.apply() weiter statt mit target.apply(...). So klappt es auch, wenn die Funktion eine eigene apply-Eigenschaft hat, die die geerbte überdeckt.
  • Für apply gibt es keine Invarianten. Der Rückgabewert kann also frei gewählt werden.
  • Leistung: Jeder Aufruf läuft durch die Falle. In sehr heißen Schleifen kann das merklich bremsen.
  • typeof proxy liefert 'function', und proxy.name bzw. proxy.length werden (ohne get-Falle) an das Ziel weitergereicht.
  • Kompatibilität: Wird von allen modernen Browsern und von Node.js 6+ unterstützt. Lässt sich nicht per Polyfill nachbilden, im Internet Explorer gibt es das also nicht.