W3docs

JavaScript Decoratoren und Weiterleitungen: call & apply

Dekoratorfunktionen in JavaScript schreiben und Aufrufe mit func.call und func.apply weiterleiten – mit Caching-Dekorator, bind und Method Borrowing.

Ein Dekorator ist eine Wrapper-Funktion: Sie nimmt eine andere Funktion entgegen und gibt eine neue Funktion zurück, die zusätzliches Verhalten – Logging, Caching, Zeitmessung, Zugriffskontrollen – rund um das Original ergänzt, ohne dessen Code zu verändern. Um Dekoratoren zu schreiben, die für jede Funktion funktionieren, braucht man eine zuverlässige Methode, eine Funktion mit einem gewählten this und gewählten Argumenten aufzurufen. Genau das bieten func.call und func.apply.

Dieses Kapitel behandelt Dekoratoren (Wrapper-Funktionen), die Weiterleitung von this und Argumenten mit call/apply, das Wiederherstellen von verlorenem Kontext mit bind und das Ausleihen von Methoden.

Hinweis: Dies betrifft Funktions-Dekoratoren – das alltägliche Muster, das in reinem JavaScript heute verfügbar ist. Die neueren, mit @ versehenen Klassen-Dekoratoren sind ein separates, fortgeschrittenes Feature (derzeit ein Stage-3-Vorschlag, der einen Transpiler benötigt) und werden hier nicht behandelt.

Was ein Dekorator ist

Ein Dekorator ist eine Funktion, die eine Zielfunktion umhüllt und eine Ersatzfunktion mit zusätzlichem Verhalten zurückgibt. Da der Wrapper die gleiche äußere Form hat, müssen Aufrufer nichts ändern.

function sum(a, b) {
  return a + b;
}

function logged(func) {
  return function (a, b) {
    console.log(`calling with ${a}, ${b}`);
    return func(a, b);
  };
}

const loggedSum = logged(sum);
console.log(loggedSum(2, 3));
// calling with 2, 3
// 5

Der Wrapper ist wiederverwendbar, lässt das Original unberührt und kann gestapelt werden. Der Nachteil oben ist, dass er nur eine Funktion mit genau zwei Argumenten und ohne this behandelt. Um jede Funktion zu umhüllen, leiten wir den Aufruf weiter.

Ein Caching-Dekorator

Ein verbreiteter Dekorator in der Praxis speichert Ergebnisse im Cache, sodass eine aufwändige Funktion pro Eingabe nur einmal ausgeführt wird. Probiere es aus:

javascript— editable

Das funktioniert für eine eigenständige Funktion. Sobald slow jedoch eine Methode ist, die this verwendet, bricht func(x) beim Aufruf – der Wrapper verliert den Objekt-Kontext. Genau hier kommen call und apply ins Spiel.

Den Aufruf weiterleiten: call und apply

call und apply rufen beide eine Funktion mit einem explizit gewählten this auf. Sie unterscheiden sich nur darin, wie Argumente übergeben werden:

  • func.call(thisArg, arg1, arg2, ...) – Argumente werden einzeln aufgelistet.
  • func.apply(thisArg, argsArray) – Argumente als einzelnes Array (oder array-ähnlicher Wert).

call

javascript— editable

apply

javascript— editable

Diese beiden Aufrufe sind gleichwertig:

func.call(obj, 1, 2, 3);
func.apply(obj, [1, 2, 3]);

Verwende call, wenn du die Argumente einzeln kennst; verwende apply, wenn du sie bereits in einem Array hast. Mit Spread-Syntax (func.call(obj, ...args)) verschwindet der Unterschied oft – siehe Rest-Parameter und Spread-Syntax.

this mit call weiterleiten

Jetzt können wir den Caching-Dekorator für Methoden reparieren. Im Wrapper ist this das Objekt, auf dem die Methode aufgerufen wurde, also leiten wir es mit func.call(this, x) weiter:

javascript— editable

Ohne func.call(this, x) wäre der interne Aufruf func(x) und this würde verloren gehen, sodass this.someMethod() fehlschlagen würde.

Alle Argumente mit apply weiterleiten

Für eine Methode mit mehreren Argumenten werden alle Argumente auf einmal weitergeleitet. Der Wrapper weiß nicht, wie viele es sind, also liest er sie aus arguments und leitet sie alle mit func.apply(this, arguments) weiter:

javascript— editable

this und arguments direkt durchzureichen wird als Aufrufweiterleitung (Call Forwarding) bezeichnet: Der Wrapper verhält sich genau wie das Original, nur mit zusätzlicher Logik darum herum.

Methoden ausleihen (Method Borrowing)

Die hash-Funktion oben verwendet einen Trick. arguments ist array-ähnlich (es hat Indizes und length), ist aber kein echtes Array und hat daher kein join. Statt es umzuwandeln, leihen wir die Array-Methode:

function hash(args) {
  return [].join.call(args, ',');
}
console.log(hash([3, 5])); // "3,5"

[].join ist Array.prototype.join. Es mit args als this aufzurufen, führt die Join-Logik auf dem array-ähnlichen Wert aus. Method Borrowing erlaubt es, eingebaute Methoden auf Objekten wiederzuverwenden, die nicht von diesem Typ sind.

bind und verlorener Kontext

call und apply rufen sofort auf. bind hingegen gibt eine neue Funktion zurück, bei der this dauerhaft gebunden ist – nützlich, wenn der Aufruf später erfolgt (ein Callback, ein Event-Handler, ein setTimeout).

Das Problem, das bind löst, ist Kontextverlust: Eine Methode von ihrem Objekt zu trennen bedeutet, dass this nicht mehr auf dieses Objekt zeigt.

javascript— editable

Für einen tieferen Einblick in die Behebung von Kontextverlust in Callbacks und den Unterschied zwischen bind, Pfeilfunktionen und call/apply siehe Funktionsbindung.

Wann was verwenden

ZielVerwende
Jetzt aufrufen mit gewähltem this, Argumente einzelnfunc.call(thisArg, a, b)
Jetzt aufrufen mit gewähltem this, Argumente als Arrayfunc.apply(thisArg, args)
Eine Funktion für späteren Aufruf mit festem this erhaltenfunc.bind(thisArg)
Eine eingebaute Methode auf einem array-ähnlichen object wiederverwendenAusleihen: [].method.call(obj, …)

Fazit

Dekoratoren umhüllen eine Funktion, um Verhalten hinzuzufügen, ohne sie zu verändern. Damit ein Wrapper für jede Funktion funktioniert – auch für Methoden – leite den ursprünglichen Aufruf mit func.call(this, ...) oder func.apply(this, arguments) weiter, verwende bind, wenn der Aufruf verzögert erfolgt, und leihe eingebaute Methoden, wenn ein object nur array-ähnlich ist. Zusammen bieten diese Techniken wiederverwendbare, kontextsichere Abstraktionen wie den oben gezeigten Caching-Dekorator.

Weiterführende Lektüre: Objekt-Methoden, "this", Funktionsobjekt, NFE und Funktionsbindung.

Übungen

Übung
Welche Aussagen beschreiben die Verwendung und die Unterschiede zwischen den Methoden `call` und `apply` in JavaScript korrekt?
Welche Aussagen beschreiben die Verwendung und die Unterschiede zwischen den Methoden `call` und `apply` in JavaScript korrekt?
Was this page helpful?