Kontrollstrukturen und Schleifen sind fundamentale Bausteine jeder Programmiersprache. Sie ermöglichen es, den Programmfluss zu steuern, Entscheidungen zu treffen und Codeblöcke wiederholt auszuführen. JavaScript bietet eine Vielzahl solcher Konstrukte, die wir in diesem Abschnitt detailliert betrachten werden.
Die if-Anweisung ist die grundlegendste Kontrollstruktur
und führt einen Codeblock aus, wenn eine Bedingung als wahr
(true) ausgewertet wird:
if (bedingung) {
// Dieser Code wird nur ausgeführt, wenn bedingung true ergibt
}Da JavaScript dynamische Typisierung verwendet, wird die Bedingung automatisch in einen booleschen Wert umgewandelt. Wie im Kapitel “Variablen, Datentypen und Typkonvertierung” beschrieben, werden dabei bestimmte Werte als “falsy” betrachtet:
// Diese Bedingungen sind alle "falsy"
if (false) {}
if (0) {}
if ("") {}
if (null) {}
if (undefined) {}
if (NaN) {}
// Alle anderen Werte sind "truthy"
if (true) {}
if (42) {}
if ("text") {}
if ({}) {}
if ([]) {}Mit else kann ein alternativer Codeblock ausgeführt
werden, wenn die Bedingung nicht erfüllt ist:
if (alter >= 18) {
console.log("Volljährig");
} else {
console.log("Minderjährig");
}Für mehrere alternative Bedingungen kann die
if-else if-else-Struktur verwendet werden:
if (note >= 90) {
console.log("Sehr gut");
} else if (note >= 80) {
console.log("Gut");
} else if (note >= 70) {
console.log("Befriedigend");
} else if (note >= 60) {
console.log("Ausreichend");
} else {
console.log("Nicht bestanden");
}Bedingte Anweisungen können beliebig geschachtelt werden, was jedoch die Lesbarkeit beeinträchtigen kann:
if (isAuthenticated) {
if (userRole === "admin") {
console.log("Admin-Bereich");
} else {
console.log("Benutzerbereich");
}
} else {
console.log("Bitte anmelden");
}In solchen Fällen kann häufig eine flachere Struktur mit logischen Operatoren oder eine frühe Rückkehr die Lesbarkeit verbessern:
// Mit logischen Operatoren
if (isAuthenticated && userRole === "admin") {
console.log("Admin-Bereich");
} else if (isAuthenticated) {
console.log("Benutzerbereich");
} else {
console.log("Bitte anmelden");
}
// Mit früher Rückkehr (in einer Funktion)
function checkAccess() {
if (!isAuthenticated) {
console.log("Bitte anmelden");
return;
}
if (userRole === "admin") {
console.log("Admin-Bereich");
} else {
console.log("Benutzerbereich");
}
}Für einfache bedingte Zuweisungen bietet der ternäre Operator eine kompakte Alternative:
// Langform mit if-else
let status;
if (alter >= 18) {
status = "erwachsen";
} else {
status = "minderjährig";
}
// Kompakte Form mit ternärem Operator
let status = alter >= 18 ? "erwachsen" : "minderjährig";Bei komplexeren Bedingungen sollte jedoch der Lesbarkeit Vorrang gegeben werden.
Die switch-Anweisung eignet sich für
Mehrfachverzweigungen basierend auf einem einzelnen Wert:
switch (wochentag) {
case "Montag":
console.log("Wochenanfang");
break;
case "Dienstag":
case "Mittwoch":
case "Donnerstag":
console.log("Wochenmitte");
break;
case "Freitag":
console.log("Fast Wochenende");
break;
case "Samstag":
case "Sonntag":
console.log("Wochenende");
break;
default:
console.log("Ungültiger Wochentag");
}Wichtig ist das break-Statement, das verhindert, dass
die Ausführung in den nächsten case fällt (der sogenannte
“Fall-Through”). In einigen Fällen kann ein bewusstes Weglassen von
break nützlich sein, um denselben Code für mehrere Fälle
auszuführen, wie im obigen Beispiel zu sehen.
Die switch-Anweisung verwendet strenge Gleichheit
(===), was bei der Arbeit mit unterschiedlichen Datentypen
zu beachten ist.
Schleifen ermöglichen die wiederholte Ausführung von Code und sind unerlässlich für die Arbeit mit Datensammlungen und iterative Algorithmen.
Die klassische for-Schleife besteht aus drei Teilen:
Initialisierung, Bedingung und Inkrement:
for (let i = 0; i < 5; i++) {
console.log(`Durchlauf ${i}`);
}Die Einzelteile können auch weggelassen werden (führt zu einer Endlosschleife, wenn keine Abbruchbedingung implementiert wird):
let i = 0;
for (;;) {
if (i >= 5) break;
console.log(`Durchlauf ${i}`);
i++;
}Die for...in-Schleife iteriert über die enumerierbaren
Eigenschaften eines Objekts, einschließlich seiner Prototypenkette:
const person = {
name: "Max",
alter: 30,
beruf: "Entwickler"
};
for (const eigenschaft in person) {
console.log(`${eigenschaft}: ${person[eigenschaft]}`);
}
// Ausgabe:
// name: Max
// alter: 30
// beruf: EntwicklerWichtig: for...in ist für Objekte
gedacht und sollte nicht für Arrays verwendet werden, da die Reihenfolge
nicht garantiert ist und auch Eigenschaften der Prototypenkette erfasst
werden können.
Die for...of-Schleife wurde mit ES6 eingeführt und
iteriert über iterierbare Objekte (Arrays, Strings, Maps, Sets
usw.):
const zahlen = [10, 20, 30, 40, 50];
for (const zahl of zahlen) {
console.log(zahl);
}
// Ausgabe: 10, 20, 30, 40, 50
const text = "JavaScript";
for (const buchstabe of text) {
console.log(buchstabe);
}
// Ausgabe: J, a, v, a, S, c, r, i, p, tIm Gegensatz zu for...in berücksichtigt
for...of nur die Werte und nicht die Indizes oder
Schlüssel.
const array = ["a", "b", "c"];
array.zusatz = "nicht iterierbar mit for...of";
// for...in gibt Indizes UND zusätzliche Eigenschaften aus
for (const index in array) {
console.log(index); // 0, 1, 2, zusatz
}
// for...of gibt nur iterierbare Werte aus
for (const wert of array) {
console.log(wert); // a, b, c
}Die while-Schleife führt einen Codeblock aus, solange
eine Bedingung erfüllt ist:
let count = 0;
while (count < 5) {
console.log(`Zähler: ${count}`);
count++;
}Die Bedingung wird vor jeder Iteration geprüft. Ist sie bereits zu
Beginn false, wird der Schleifenkörper nie ausgeführt.
Die do...while-Schleife ist ähnlich wie
while, prüft die Bedingung jedoch erst nach der Ausführung
des Schleifenkörpers:
let count = 0;
do {
console.log(`Zähler: ${count}`);
count++;
} while (count < 5);Dadurch wird der Schleifenkörper mindestens einmal ausgeführt, auch
wenn die Bedingung bereits zu Beginn false ist:
let count = 10;
do {
console.log("Dieser Text wird trotz count >= 5 einmal ausgegeben");
} while (count < 5);JavaScript bietet Anweisungen zur expliziten Steuerung des Schleifenverhaltens:
Mit break wird die innerste umgebende Schleife sofort
verlassen:
for (let i = 0; i < 10; i++) {
if (i === 5) {
break; // Beendet die Schleife vorzeitig
}
console.log(i);
}
// Ausgabe: 0, 1, 2, 3, 4Mit continue wird die aktuelle Iteration abgebrochen und
mit der nächsten fortgefahren:
for (let i = 0; i < 10; i++) {
if (i % 2 === 0) {
continue; // Überspringt gerade Zahlen
}
console.log(i);
}
// Ausgabe: 1, 3, 5, 7, 9Mit Labels können Schleifen benannt werden, um sie gezielt mit
break oder continue anzusprechen:
äußereSchleife: for (let i = 0; i < 3; i++) {
innereSchleife: for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1) {
break äußereSchleife; // Beendet beide Schleifen
}
console.log(`i=${i}, j=${j}`);
}
}Dieses Feature wird in der Praxis selten verwendet, kann aber in komplexen verschachtelten Schleifen nützlich sein.
In modernem JavaScript gibt es funktionale Alternativen zu traditionellen Schleifen, insbesondere für die Arbeit mit Arrays:
Die forEach-Methode führt eine Funktion für jedes
Element eines Arrays aus:
const zahlen = [1, 2, 3, 4, 5];
zahlen.forEach((zahl, index) => {
console.log(`Index ${index}: ${zahl}`);
});Im Gegensatz zu einer Schleife kann forEach nicht mit
break oder continue gesteuert werden - die
Funktion wird für jedes Element ausgeführt.
Die map-Methode erstellt ein neues Array mit den
Ergebnissen des Aufrufs einer Funktion für jedes Element:
const zahlen = [1, 2, 3, 4, 5];
const quadriert = zahlen.map(zahl => zahl * zahl);
console.log(quadriert); // [1, 4, 9, 16, 25]Die filter-Methode erstellt ein neues Array mit allen
Elementen, die eine Bedingung erfüllen:
const zahlen = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const geradeZahlen = zahlen.filter(zahl => zahl % 2 === 0);
console.log(geradeZahlen); // [2, 4, 6, 8, 10]Die reduce-Methode reduziert ein Array auf einen
einzelnen Wert, indem sie eine Akkumulatorfunktion auf jedes Element
anwendet:
const zahlen = [1, 2, 3, 4, 5];
const summe = zahlen.reduce((akkumulator, aktuellerWert) => {
return akkumulator + aktuellerWert;
}, 0);
console.log(summe); // 15
// Komplexeres Beispiel: Zählen von Elementen nach Kategorie
const früchte = ['Apfel', 'Banane', 'Apfel', 'Orange', 'Banane', 'Apfel'];
const zählung = früchte.reduce((acc, frucht) => {
acc[frucht] = (acc[frucht] || 0) + 1;
return acc;
}, {});
console.log(zählung); // { Apfel: 3, Banane: 2, Orange: 1 }Die find-Methode gibt das erste Element zurück, das eine
Bedingung erfüllt:
const benutzer = [
{ id: 1, name: "Max" },
{ id: 2, name: "Anna" },
{ id: 3, name: "Tim" }
];
const gefundenerBenutzer = benutzer.find(user => user.id === 2);
console.log(gefundenerBenutzer); // { id: 2, name: "Anna" }
// findIndex gibt den Index statt des Elements zurück
const index = benutzer.findIndex(user => user.id === 2);
console.log(index); // 1Die some-Methode prüft, ob mindestens ein Element eine
Bedingung erfüllt:
const zahlen = [1, 2, 3, 4, 5];
const hatGerade = zahlen.some(zahl => zahl % 2 === 0);
console.log(hatGerade); // trueDie every-Methode prüft, ob alle Elemente eine Bedingung
erfüllen:
const zahlen = [1, 2, 3, 4, 5];
const allePositiv = zahlen.every(zahl => zahl > 0);
console.log(allePositiv); // true
const alleGerade = zahlen.every(zahl => zahl % 2 === 0);
console.log(alleGerade); // falseDie Wahl der richtigen Schleife oder Iteration kann Auswirkungen auf die Performance haben:
for-Schleifen sind in der Regel am
performantesten, besonders bei großen Arrays.for...of ist etwas langsamer, aber sehr leserlich und
für die meisten Anwendungen ausreichend schnell.forEach und andere Array-Methoden haben einen kleinen
Overhead durch die Funktionsaufrufe.for...in ist am langsamsten und sollte für Arrays
vermieden werden.Ein einfaches Beispiel zur Demonstration:
const großesArray = Array(1000000).fill(0).map((_, i) => i);
console.time('for');
for (let i = 0; i < großesArray.length; i++) {
// Verarbeitung
}
console.timeEnd('for');
console.time('for...of');
for (const element of großesArray) {
// Verarbeitung
}
console.timeEnd('for...of');
console.time('forEach');
großesArray.forEach(element => {
// Verarbeitung
});
console.timeEnd('forEach');const namen = ["Max", "Anna", "Tim"];
// Klassisch
for (let i = 0; i < namen.length; i++) {
console.log(namen[i]);
}
// Moderner und lesbarer
for (const name of namen) {
console.log(name);
}
// Funktional mit Zugriff auf Index
namen.forEach((name, index) => {
console.log(`${index + 1}. ${name}`);
});const person = {
name: "Anna",
alter: 28,
beruf: "Entwicklerin"
};
// Mit for...in
for (const eigenschaft in person) {
console.log(`${eigenschaft}: ${person[eigenschaft]}`);
}
// Mit Object.entries() (ES8)
for (const [eigenschaft, wert] of Object.entries(person)) {
console.log(`${eigenschaft}: ${wert}`);
}
// Mit Object.keys()
Object.keys(person).forEach(eigenschaft => {
console.log(`${eigenschaft}: ${person[eigenschaft]}`);
});// Map
const benutzerRollen = new Map([
["max", "admin"],
["anna", "editor"],
["tim", "user"]
]);
for (const [benutzer, rolle] of benutzerRollen) {
console.log(`${benutzer} ist ${rolle}`);
}
benutzerRollen.forEach((rolle, benutzer) => {
console.log(`${benutzer} ist ${rolle}`);
});
// Set
const uniqueNumbers = new Set([1, 2, 3, 2, 1]);
for (const num of uniqueNumbers) {
console.log(num); // 1, 2, 3
}
uniqueNumbers.forEach(num => {
console.log(num); // 1, 2, 3
});Mit ES2018 wurde asynchrone Iteration eingeführt, die besonders nützlich für asynchrone Datenströme ist:
async function processItems() {
const items = getAsyncItemsSource();
for await (const item of items) {
// Verarbeite jedes Item asynchron
await processItem(item);
}
}Dieses Thema wird im Kapitel “Asynchrones JavaScript” ausführlicher behandelt.
map, filter und andere Methoden, die neue
Arrays erstellen, statt bestehende zu modifizieren.break
oder return, um Schleifen frühzeitig zu beenden, wenn das
Ziel erreicht ist.false
werden.// Schlecht: Tiefe Verschachtelung
for (let i = 0; i < rows.length; i++) {
for (let j = 0; j < columns.length; j++) {
for (let k = 0; k < depth.length; k++) {
// Komplexe Verarbeitung
}
}
}
// Besser: Extraktion in Funktion
function processCell(row, column, depth) {
// Verarbeitung
}
for (let i = 0; i < rows.length; i++) {
for (let j = 0; j < columns.length; j++) {
for (let k = 0; k < depth.length; k++) {
processCell(rows[i], columns[j], depth[k]);
}
}
}
// Noch besser: Funktionale Ansätze nutzen
rows.forEach(row => {
columns.forEach(column => {
depth.forEach(depth => {
processCell(row, column, depth);
});
});
});