Hallo Julia
Ich habe folgendes Programm und muss die Ausgabe bestimmen:
function f1(x) { return y => x * y; }; const f3 = f1(3); const f5 = f1(5); alert(f3(11) + " " + f5(7));
Ausgabe: 33 35
Wie kommt es zu dieser Ausgabe?
Wie schon richtig angemerkt wurde, hast du in f1
eine Funktion, die eine Funktion als Ergebnis zurückgibt. Genauer gesagt wird eine anonyme Funktion zurückgegeben, für die bei jedem Aufruf von f1
ein neues Funktionsobjekt erstellt wird. Es ist wichtig für das Verständnis, dass hier immer eine neue Funktion erzeugt und zurückgegeben wird:
function f1(x) {
return y => x * y;
}
f1(3) === f1(3); // false
Funktionen, die andere Funktionen als Argumente entgegennehmen oder die wie in deinem Beispiel eine Funktion als Wert zurückgeben, nennt man Funktionen höherer Ordnung. In Sprachen wie JavaScript, die es erlauben mit Funktionen im Wesentlichen so zu verfahren wie mit anderen Werten auch, die es also zum Beispiel erlauben Funktionen in Variablen zu hinterlegen oder sie wie hier innerhalb einer Rückgabeanweisung zu definieren, werden solche Konstrukte häufig genutzt um über bestimmte Abläufe zu abstrahieren.
In diesem Fall hast du originär eine binäre Operation, nämlich die skalare Multiplikation wie man sie aus der Schulmathematik kennt. Wolltest du hier mehrfach mit demselben Faktor multiplizieren, müsstest du diesen grundsätzlich immer angeben:
2 * 5; // 10
2 * 7; // 14
2 * 8; // 16
Das wird schnell repetitiv und mit jeder Wiederholung erhöht sich die Wahrscheinlichkeit, dass du einen Fehler einbaust. Bist du also in der Situation, öfter mit einem bestimmten Wert multiplizieren zu müssen, bietet es sich an, diese Aufgabe in eine eigene Funktion auszulagern. Das könnte dann etwa so aussehen:
function multiply2(x) {
return 2 * x;
}
multiply2(5); // 10
Das sieht schon besser aus, aber was, wenn es auch mehrere Stellen gibt, an denen du den Wert 3
als Faktor verwenden willst, schreibst du dir dann eine Funktion multiply3
? Hier merkst du, dass du eigentlich eine Funktion möchtest, die es dir erlaubt, einen beliebigen Faktor festzusetzen, die also über die Aufgabe „multipliziere mit einer bestimmten Zahl“ abstrahiert. Das Ergebnis dieser Überlegungen ist dann deine Funktion f1
.
Aber wie funktioniert das Festlegen des einen Operanden genau?
Stellen wir uns kurz den allgemeinen Ablauf vor: Du rufst die Funktion f1
mit einem Wert auf, sagen wir 5
. Wird die Kontrolle vom aufrufenden Kontext an die aufgerufene Funktion f1
übergeben, dann wird der Wert 5
an den formalen Parameter x
gebunden. Damit wird aber zunächst gar nichts gemacht. Die einzige Anweisung der Funktion besteht darin, eine anonyme Funktion zu erzeugen und sie an den Aufrufer zurückzugeben. Interessant wird es erst, wenn nun diese von f1
zurückgegebene einstellige Funktion aufgerufen wird:
function f1(x) {
return y => x * y;
}
const f5 = f1(5);
f5(3); // 15
Der Parameter y
der anonymen Funktion wird hier mit dem Wert 3
initialisiert, das heißt, in dem arithmetischen Ausdruck wird y
durch 3
ersetzt. Es steht da aber auch noch ein x
, und ein solches ist innerhalb der Funktion, die jetzt f5
heißt, nicht definiert.
Nun ist es so, dass Funktionen in gewisser Weise über ein Gedächtnis verfügen. – Sie merken sich, in welcher Umgebung sie definiert wurden. Das bedeutet, auch wenn Funktionen wie oben beschrieben Bürger erster Klasse sind und beliebig zwischen verschiedenen Teilen eines Programms hin- und hergeschoben werden können, können sie weiterhin auf Variablen zugreifen, die zum Zeitpunkt ihrer Erzeugung für die Funktion sichtbar waren.
Als die Funktion f5
erzeugt wurde, existierte in ihrer lexikalischen Umgebung, nämlich dem Gültigkeitsbereich, der mit dem Funktionskontext von f1
assoziiert war, eine Variable namens x
, die an den Wert 5
gebunden war. (Aus Sicht des Körpers einer Funktion ist ein Parameter nichts anderes als eine lokale Variable.) Diese Variable x
wurde nach der Abarbeitung von f1
nicht verworfen, sondern konserviert.
Beim Aufruf von f5
wird nun zunächst geprüft, ob in f5
selbst eine Variable bzw. ein Parameter mit dem Bezeichner x
existiert. Da dies offenbar nicht der Fall ist, wird in dem Gültigkeitsbereich nachgesehen, in dem f5
definiert wurde. Dort wird eine entsprechende Variable gefunden und deren Wert 5
in den Ausdruck eingesetzt:
const f5 = f1(5); // y => 5 * y
f5(3); // 3 => 5 * 3
Greift eine Funktion wie hier f5
auf Variablen oder Funktionen zurück, die in der Umgebung definiert waren, in der die Funktion erzeugt wurde, dann spricht man auch von einem Funktionsabschluss, oder im Englischen: Closure. Diese Funktionalität ist Grundlage für Techniken wie Currying – die Zerlegung einer mehrstelligen Funktion in mehrere einstellige Funktionen. Dafür ist deine Funktion f1
ein Beispiel.
Viele Grüße,
Orlok
„Dance like it hurts.
Make love like you need money.
Work when people are watching.“ — Dogbert