Scriptease.js: Ăśber den Umgang mit Funktionen

Das Anheben von Variablendeklarationen bei JavaScript betrifft nicht nur Werte und Objekte, sondern unter Umständen auch Funktionsausdrücke: Schließlich sind Funktionen in der funktionalen Sprache nichts anderes als Objekte. heise Developer erläutert, worauf es beim Verwenden von Funktionen zu achten gilt.

vorlesen Druckansicht 14 Kommentare lesen
Lesezeit: 10 Min.
Von
  • Golo Roden
Inhaltsverzeichnis

Das Anheben von Variablendeklarationen bei JavaScript betrifft nicht nur Werte und Objekte, sondern unter Umständen auch Funktionsausdrücke: Schließlich sind Funktionen in der funktionalen Sprache nichts anderes als Objekte. heise Developer erläutert, worauf es beim Verwenden von Funktionen zu achten gilt.

Wie früher beschrieben, dient das JavaScript-Schlüsselwort var der Deklaration von Variablen. Anders als in einigen anderen Programmiersprachen können Variablen in JavaScript jedoch nicht nur Werte und Objekte aufnehmen, sondern auch Funktionen. Das klappt, da Funktionen in JavaScript letztlich nichts anderes sind als Objekte, die ausführbaren Code enthalten und sich daher aufrufen lassen.

Mehr Infos

Was steckt hinter Scriptease.js?

JavaScript erlernt sich deutlich leichter als die meisten anderen Programmiersprachen. Dennoch ist die Sprache nicht per se einfach, ganz im Gegenteil. Die Palette reicht von exotischen und selten verwendeten Schlüsselwörtern bis hin zu unerklärlichem und scheinbar widersprüchlichem Verhalten. Die Kolumne Scriptease.js führt in die Feinheiten von JavaScript ein und vermittelt sowohl subtile Eigenarten der Sprache als auch bemerkenswerte Interna.

Der Vorteil dieses Vorgehens liegt auf der Hand: Funktionen werden, wie alle anderen Werte, als Bürger erster Klasse angesehen und können daher beispielsweise auch als Parameter an eine weitere Funktion übergeben oder als Rückgabewert von einer Funktion erhalten werden. Das ermöglicht die Implementierung von Funktionen höherer Ordnung, die ihrerseits Funktionen verarbeiten. Was in der Theorie zunächst geradlinig erscheinen mag, weist in der Praxis jedoch einige Stolperfallen auf.

Weist der Entwickler nämlich einer Variablen eine Funktion zu, greift der in der vergangenen Folge beschriebene Effekt der Anhebung von Variablendeklarationen: JavaScript verschiebt die Deklaration der Funktion während der Laufzeit an den Anfang der deklarierenden Funktion. Die gleichzeitige Deklaration und Definition

var add = function (first, second) {
return first + second;
};

wird daher zur Laufzeit in zwei Teile aufgespalten:

var add;
add = function (first, second) {
return first + second;
};

Befindet sich zwischen der ursprünglichen Variante und dem Beginn der enthaltenen Funktion weiterer Code, wird die Deklaration vor diesen verschoben. Allerdings betrifft das wohlgemerkt nur die Deklaration, nicht die Definition: Das bedeutet, dass die Variable add zu Beginn den Wert undefined aufweist und erst nach der Zuweisung die zugehörige Funktion enthält.

Das bedeutet aber auch, dass die eigentliche Funktion erst nach dem DurchfĂĽhren der Definition fĂĽr den Aufrufer zur VerfĂĽgung steht. In anderen Worten: Wird eine Funktion in JavaScript mit dem var-SchlĂĽsselwort definiert, liegt sie erst nach der Definition vor, auch wenn ihre Deklaration durch das Hoisting angehoben wird. Versucht man dennoch, einen Aufruf auszufĂĽhren, meldet die Laufzeitumgebung einen TypeError und weist darauf hin, dass undefined keine aufrufbare Funktion ist.

Kein Problem stellt hingegen die Rekursion dar (siehe Kasten "Rekursion in JavaScript – eine schlechte Idee?): Da sowohl die Deklaration als auch die Definition in jedem Fall vor dem rekursiven Aufruf erfolgen, funktioniert das ohne Weiteres:

var fac = function (n) {
if (n === 1) {
return 1;
} else {
return fac(n - 1) * n;
}
};
console.log(fac(5)); // => 120
Mehr Infos

Rekursion in JavaScript – eine schlechte Idee?

Die im Text enthaltene Aussage, "kein Problem [stelle] hingegen die Rekursion dar", bezieht sich ausschließlich auf die Syntax von JavaScript: Diese ermöglicht nämlich die Verwendung von Rekursion, ohne besondere Vorkehrungen wie in einigen anderen Sprachen treffen zu müssen (in F# sind Funktionen, die rekursiv aufrufbar sein sollen, beispielsweise mit dem Schlüsselwort rec zu versehen.

Doch nicht alles, was syntaktisch möglich ist,empfiehlt sich auch für den alltäglichen Einsatz. Da die gängigen Laufzeitumgebungen für JavaScript keine Unterstützung für Endrekursion enthalten, werden rekursive Funktionsaufrufe in JavaScript unter Umständen deutlich langsamer ausgeführt als potenziell notwendig.

Zunächst war die Unterstützung von Endrekursion für ECMAScript 4 geplant, wurde dann aber nicht umgesetzt. Unter Umständen erfolgt das nun im Rahmen von ECMAScript 6. Bis dahin gilt, dass Rekursion in JavaScript vermieden und gegebenenfalls durch eine iterative Variante des Codes ersetzt werden sollte.

Da die Funktion in den bisherigen Beispielen stets auf der rechten Seite des Zuweisungsoperators aufgetreten ist und dort bei einer Zuweisung an eine Variable ĂĽblicherweise AusdrĂĽcke stehen, gilt auch diese Definition einer Funktion als Ausdruck: Dabei handelt es sich um einen sogenannten Funktionsausdruck.

Das zeigt sich auch darin, dass man FunktionsausdrĂĽcke im Rahmen anderer AusdrĂĽcke verwenden kann, wie das folgende Beispiel verdeutlicht:

var a,
b = function () {
console.log('foo');
};
(a || b)();

Da die Variable a in diesem Fall den Wert undefined enthält, was der ||-Operator zu false evaluiert, wird stets die Funktion b aufgerufen.

Dieses Vorgehen stellt allerdings nicht das einzige Mittel in JavaScript zur Definition einer Funktion dar. Alternativ zu Funktionsausdrücken lassen sich auch sogenannte Funktionsanweisungen verwenden. Syntaktisch zeichnet sich eine solche Anweisung durch das Fehlen des Schlüsselworts var und die Angabe eines Funktionsnamens hinter dem Schlüsselwort function aus. Der semantische Unterschied liegt darin, dass es sich nicht um einen Ausdruck, sondern eine Anweisung handelt. Die Fakultätsfunktion ließe sich als Funktionsanweisung wie folgt definieren:

console.log(fac(5)); // => 120
function fac (n) {
if (n === 1) {
return 1;
} else {
return fac(n - 1) * n;
}
};

Wie das Beispiel zeigt, unterscheiden sich Funktionsanweisungen von Funktionsausdrücken auch darin, dass sie sich bereits aufrufen lassen, bevor sie definiert werden. Das funktioniert, da JavaScript vor dem Ausführen des Codes zunächst alle Definitionen von Funktionsanweisungen sucht, diese in einer internen Liste speichert, sodass sie später direkt zur Verfügung stehen.

Daraus ergibt sich ein gewisser Komfort, da der Entwickler nicht mehr auf die Reihenfolge der
Funktionsdefinitionen achten muss. Allerdings Darüber hinaus eröffnen Funktionsanweisungen auch die Möglichkeit, Funktionen zu schreiben, die einander gegenseitig aufrufen: ein Vorhaben, das ausschließlich mit Funktionsausdrücken nicht realisierbar ist, da eine der beiden Funktionen stets vor der anderen zu definieren ist das mit Funktionsausdrücken nur unter der Auflage realisierbar ist, dass beide Funktionsdefinitionen bereits vor deren Aufruf verarbeitet wurden. Das folgende Beispiel demonstriert dieses Vorgehen:

function even (n) {
if (n % 2 === 0) {
console.log('even');
} else {
odd(n);
}
}
function odd (n) {
if (n % 2 !== 0) {
console.log('odd');
} else {
even(n);
}
}