Funktionen bilden einen zentralen Baustein der JavaScript-Programmierung. Sie ermöglichen die Organisation von Code in wiederverwendbare, modulare Einheiten und unterstützen verschiedene Programmierstile - von prozedural über objektorientiert bis hin zu funktional. Eng mit Funktionen verbunden sind die Konzepte von Scopes und Closures, die maßgeblich beeinflussen, wie Variablen in JavaScript verwaltet werden.
JavaScript bietet verschiedene Arten, Funktionen zu definieren, die jeweils eigene charakteristische Eigenschaften und Anwendungsfälle haben.
Die klassische Form, eine Funktion zu definieren, ist die Funktionsdeklaration:
function willkommen(name) {
return `Hallo ${name}!`;
}Ein Merkmal der Funktionsdeklaration ist das “Hoisting” - die Funktion wird zur Kompilierzeit an den Anfang ihres Scopes “gehoben” und kann bereits vor ihrer Definition im Code aufgerufen werden:
console.log(willkommen("Max")); // Funktioniert trotz Aufruf vor Definition
function willkommen(name) {
return `Hallo ${name}!`;
}Bei einem Funktionsausdruck wird die Funktion einer Variablen zugewiesen:
const quadrieren = function(zahl) {
return zahl * zahl;
};Im Gegensatz zu Funktionsdeklarationen werden Funktionsausdrücke nicht gehoisted und können erst nach ihrer Definition aufgerufen werden.
Funktionsausdrücke können benannt werden, was für rekursive Aufrufe oder bessere Debugging-Informationen nützlich ist:
const fakultät = function fak(n) {
if (n <= 1) return 1;
return n * fak(n - 1);
};Mit ES6 wurden Pfeilfunktionen eingeführt, die eine kompaktere Syntax bieten:
// Grundlegende Syntax
const addieren = (a, b) => {
return a + b;
};
// Bei einem einzelnen Parameter können die Klammern entfallen
const verdoppeln = x => x * 2;
// Bei einem Einzeiler mit direkter Rückgabe können geschweifte Klammern entfallen
const halbieren = x => x / 2;Pfeilfunktionen haben einige wichtige Besonderheiten:
this lexikalisch (übernehmen es aus dem
umgebenden Kontext)arguments-Objektyield und können nicht als
Generatoren dienenDie lexikalische Bindung von this macht Pfeilfunktionen
besonders nützlich für Callbacks:
const timer = {
sekunden: 0,
start() {
// Pfeilfunktion behält das 'this' von timer bei
setInterval(() => {
this.sekunden++;
console.log(this.sekunden);
}, 1000);
}
};JavaScript bietet noch weitere spezialisierte Funktionsarten:
Generatorfunktionen: Können ihre Ausführung pausieren und später fortsetzen
function* zähler() {
let i = 0;
while (true) {
yield i++;
}
}Asynchrone Funktionen: Vereinfachen den Umgang mit Promises
async function datenLaden() {
const response = await fetch('https://api.beispiel.de/daten');
return await response.json();
}Diese spezialisierten Funktionstypen werden in den Kapiteln “Generatoren und Iteratoren” und “Asynchrones JavaScript” detaillierter behandelt.
JavaScript bietet flexible Möglichkeiten, mit Funktionsparametern umzugehen.
In ihrer einfachsten Form nehmen Funktionen eine feste Anzahl benannter Parameter entgegen:
function grüßen(vorname, nachname) {
return `Hallo ${vorname} ${nachname}!`;
}JavaScript erzwingt keine strenge Übereinstimmung zwischen deklarierten Parametern und übergebenen Argumenten:
undefined behandeltarguments-Objekt zugänglich)Mit ES6 können Standardwerte für Parameter definiert werden:
function konfigurieren(optionen = {}, debugModus = false) {
// optionen ist ein leeres Objekt, wenn kein Wert übergeben wird
// debugModus ist false, wenn kein Wert übergeben wird
}Standardparameter werden nur aktiviert, wenn das Argument
undefined ist oder fehlt:
function log(nachricht, level = "info") {
console.log(`[${level}]: ${nachricht}`);
}
log("Test"); // [info]: Test
log("Fehler", "error"); // [error]: Fehler
log("Seltsam", null); // [null]: Seltsam - null ist ein gültiger Wert, überschreibt den StandardRest-Parameter ermöglichen die Verarbeitung einer beliebigen Anzahl von Argumenten als Array:
function summe(...zahlen) {
return zahlen.reduce((summe, zahl) => summe + zahl, 0);
}
summe(1, 2, 3, 4, 5); // 15Rest-Parameter müssen der letzte Parameter in der Funktionsdefinition
sein und bieten eine modernere Alternative zum
arguments-Objekt.
Mit Objektdestrukturierung können Parameter übersichtlicher gestaltet werden:
function renderBenutzer({ name, email, rolle = "Benutzer" }) {
// Parameter werden direkt aus dem übergebenen Objekt extrahiert
// rolle erhält einen Standardwert, falls nicht im Objekt vorhanden
}
// Aufruf
renderBenutzer({ name: "Max Mustermann", email: "max@beispiel.de" });Der “Scope” definiert die Sichtbarkeit und Lebensdauer von Variablen. JavaScript verwendet lexikalisches Scoping, bei dem der Scope einer Variablen durch ihre Position im Quellcode zur Schreibzeit bestimmt wird.
JavaScript kennt verschiedene Arten von Scopes:
{}) mit let oder const
deklariert werdenDie Art der Variablendeklaration bestimmt den Scope-Typ:
var erzeugt Funktions-scoped Variablen (sichtbar in der
gesamten Funktion)let und const (ES6) erzeugen Block-scoped
Variablen (nur im deklarierenden Block sichtbar)function scopeDemo() {
var functionScoped = "Ich bin in der ganzen Funktion sichtbar";
if (true) {
var auchFunctionScoped = "Auch ich bin überall in der Funktion sichtbar";
let blockScoped = "Ich bin nur in diesem if-Block sichtbar";
console.log(functionScoped); // Funktioniert
console.log(blockScoped); // Funktioniert
}
console.log(auchFunctionScoped); // Funktioniert
// console.log(blockScoped); // ReferenceError
}Scopes sind hierarchisch verschachtelt. Innere Scopes haben Zugriff auf Variablen aus äußeren Scopes, aber nicht umgekehrt:
const global = "Ich bin global";
function äußereFunktion() {
const äußer = "Ich bin in der äußeren Funktion";
function innereFunktion() {
const inner = "Ich bin in der inneren Funktion";
console.log(global); // Zugriff auf globale Variable möglich
console.log(äußer); // Zugriff auf Variable aus äußerem Scope möglich
}
innereFunktion();
// console.log(inner); // ReferenceError - kein Zugriff auf inneren Scope
}“Hoisting” beschreibt das Verhalten, bei dem Deklarationen (nicht Initialisierungen) von Variablen und Funktionen an den Anfang ihres Scopes gehoben werden:
var-Variablen werden deklariert (mit Wert
undefined), aber nicht initialisiertlet/const-Variablen werden technisch
gehoisted, bleiben aber in der “Temporal Dead Zone” bis zur
tatsächlichen Deklaration im Codeconsole.log(gehoisteteFunktion()); // "Ich wurde gehoistet" - funktioniert
function gehoisteteFunktion() {
return "Ich wurde gehoistet";
}
console.log(varVariable); // undefined - deklariert, aber nicht initialisiert
var varVariable = "Ich bin eine var-Variable";
// console.log(letVariable); // ReferenceError - temporal dead zone
let letVariable = "Ich bin eine let-Variable";Eine Closure entsteht, wenn eine Funktion auf Variablen aus ihrem äußeren lexikalischen Scope zugreift, selbst wenn sie außerhalb dieses Scopes ausgeführt wird. Dies ist eines der mächtigsten Konzepte in JavaScript.
function äußereFunktion() {
const message = "Hallo von außen!";
function innereFunktion() {
console.log(message); // Greift auf Variable aus äußerem Scope zu
}
return innereFunktion;
}
const meineClosure = äußereFunktion();
meineClosure(); // "Hallo von außen!" - auch nach Abschluss von äußereFunktionDie zurückgegebene innereFunktion “erinnert” sich an
ihre Umgebung - sie behält Zugriff auf alle Variablen, die zum Zeitpunkt
ihrer Definition im Scope waren, selbst wenn die
äußereFunktion bereits abgeschlossen ist.
Closures finden in verschiedenen Szenarien Anwendung:
1. Datenprivatsheit und Kapselung
function createCounter() {
let count = 0; // Private Variable
return {
increment() { return ++count; },
decrement() { return --count; },
getValue() { return count; }
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
// Der direkte Zugriff auf count ist nicht möglich2. Funktionsfabriken
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplier(2);
const triple = multiplier(3);
double(5); // 10
triple(5); // 153. Ereignishandler mit Zustandsspeicherung
function createButtonHandler(buttonName) {
let clickCount = 0;
return function() {
clickCount++;
console.log(`${buttonName} wurde ${clickCount} mal geklickt`);
};
}
const handleButtonA = createButtonHandler("Button A");Ein klassisches Problem tritt auf, wenn Closures in Schleifen erstellt werden:
// Problematisch
function createFunctions() {
var functions = [];
for (var i = 0; i < 3; i++) {
functions.push(function() {
console.log(i);
});
}
return functions;
}
var funcs = createFunctions();
funcs[0](); // 3 (nicht 0 wie erwartet)
funcs[1](); // 3
funcs[2](); // 3Alle Funktionen greifen auf dieselbe i-Variable zu, die
nach Schleifenende den Wert 3 hat.
Mit ES6 löst let dieses Problem elegant:
function createFunctions() {
const functions = [];
for (let i = 0; i < 3; i++) {
// let erzeugt für jeden Schleifendurchlauf einen neuen Block-Scope
functions.push(function() {
console.log(i);
});
}
return functions;
}
const funcs = createFunctions();
funcs[0](); // 0
funcs[1](); // 1
funcs[2](); // 2Das this-Keyword in JavaScript verweist auf das
Ausführungskontext-Objekt und verhält sich anders als in vielen anderen
Sprachen. Der Wert von this wird dynamisch zur Laufzeit
bestimmt und hängt davon ab, wie eine Funktion aufgerufen wird.
Globaler Kontext: Im globalen Scope verweist
this auf das globale Objekt (z.B. window im
Browser)
Funktionsaufruf: Bei einem einfachen
Funktionsaufruf ist this im strikten Modus
undefined, sonst das globale Objekt
Methodenaufruf: Bei einem Methodenaufruf
verweist this auf das Objekt, auf dem die Methode
aufgerufen wird
Konstruktoraufruf: Mit new verweist
this auf die neu erstellte Instanz
Explizite Bindung: Mit .call(),
.apply() oder .bind() kann this
explizit gesetzt werden
// Als Methode
const person = {
name: "Max",
greet() {
return `Hallo, ich bin ${this.name}`;
}
};
person.greet(); // "Hallo, ich bin Max"
// Als einfache Funktion
const greetFunc = person.greet;
greetFunc(); // "Hallo, ich bin undefined" (oder Fehler im strikten Modus)
// Explizite Bindung
const anna = { name: "Anna" };
person.greet.call(anna); // "Hallo, ich bin Anna"Pfeilfunktionen haben kein eigenes this, sondern
übernehmen es aus dem umgebenden lexikalischen Kontext:
const objekt = {
daten: [1, 2, 3],
// Problematisch mit regulärer Funktion
prozessiereTraditional: function() {
console.log(this.daten); // [1, 2, 3]
this.daten.forEach(function(item) {
// this ist hier nicht mehr objekt
console.log(this.daten, item); // undefined, 1/2/3
});
},
// Besser mit Pfeilfunktion
prozessiereMitPfeil: function() {
console.log(this.daten); // [1, 2, 3]
this.daten.forEach(item => {
// this bleibt objekt dank lexikalischer Bindung
console.log(this.daten, item); // [1, 2, 3], 1/2/3
});
}
};Da Funktionen in JavaScript First-Class-Objekte sind, können sie:
Diese Eigenschaften ermöglichen funktionale Programmiertechniken:
Funktionen, die andere Funktionen als Parameter akzeptieren oder zurückgeben:
// Nimmt eine Funktion als Parameter
function mapArray(array, transformFn) {
const result = [];
for (let i = 0; i < array.length; i++) {
result.push(transformFn(array[i], i));
}
return result;
}
// Gibt eine Funktion zurück
function createValidator(regex) {
return function(value) {
return regex.test(value);
};
}
const isEmail = createValidator(/^[^@]+@[^@]+\.[^@]+$/);
isEmail("test@example.com"); // trueReine Funktionen haben keine Seiteneffekte und liefern bei gleichen Eingaben immer gleiche Ausgaben:
// Reine Funktion
function add(a, b) {
return a + b;
}
// Nicht reine Funktion (mit Seiteneffekt)
let total = 0;
function addToTotal(value) {
total += value; // Seiteneffekt: Ändert Zustand außerhalb
return total;
}Das Kombinieren mehrerer Funktionen zu einer neuen Funktion:
function compose(f, g) {
return function(x) {
return f(g(x));
};
}
const double = x => x * 2;
const increment = x => x + 1;
const doubleAndIncrement = compose(increment, double);
doubleAndIncrement(3); // 7 (= (3*2) + 1)