JavaScript kennt drei klassische Schleifen-Formen, die fast unverändert aus C übernommen sind: for mit Init/Bedingung/Update in einem Header, while mit Vor-Test und do/while mit Nach-Test. Daneben gibt es die iterator-basierten Formen for...of und for...in (eigener Artikel) sowie funktionale Methoden wie forEach, map und reduce. Dieser Artikel konzentriert sich auf die drei klassischen Formen — wo jede ihre Stärke hat, welche Fallen typisch sind und warum sie trotz moderner Alternativen weiter eine Rolle spielen.
Die for-Schleife — Zähler-Klassiker
Der for-Header hat drei Teile, getrennt durch Semikolons: Init, Bedingung, Update. Init läuft einmal vor dem ersten Durchlauf, Bedingung wird vor jedem Durchlauf geprüft, Update läuft nach jedem Durchlauf.
for (let i = 0; i < 3; i++) {
console.log('Index', i);
}Index 0
Index 1
Index 2Jeder der drei Teile ist optional. for (;;) ist die kanonische Endlos-Schleife — der häufigste Anwendungsfall ist eine Schleife, die nur per break von innen verlassen wird.
// Endlos-Schleife mit break als Exit
let n = 0;
for (;;) {
n++;
if (n >= 3) break;
}
console.log('n =', n); // 3
// Init außen, Update außen — for nur als Bedingungs-Wrapper
let i = 0;
for (; i < 3; ) {
console.log(i);
i++;
}n = 3
0
1
2let im Init-Teil ist seit ES2015 das idiomatische Default — die Zählvariable ist auf den Schleifen-Block beschränkt, jeder Durchlauf bekommt eine neue Bindung. Das macht Closures innerhalb der Schleife sicher (siehe Falle weiter unten).
Mehrere Variablen im for-Header
Init und Update dürfen Komma-Listen sein — mehrere Variablen, mehrere Updates.
// Zwei Zeiger, einer von vorn, einer von hinten
const arr = [1, 2, 3, 4, 5];
for (let i = 0, j = arr.length - 1; i < j; i++, j--) {
console.log(arr[i], '↔', arr[j]);
}1 ↔ 5
2 ↔ 4Das ist das klassische Two-Pointer-Pattern — bei Palindrom-Checks, sortierten Arrays und ähnlichen Aufgaben. In funktionalen Pendants gibt es so etwas nicht direkt; daher bleibt for für diese Fälle die erste Wahl.
Die while-Schleife — kopfgesteuert
while (bed) { ... } prüft die Bedingung vor jedem Durchlauf. Wenn die Bedingung initial falsy ist, läuft die Schleife null Mal.
let zeichen = '';
while (zeichen.length < 5) {
zeichen += '*';
console.log(zeichen);
}*
**
***
****
*****while ist die richtige Wahl, wenn die Anzahl der Durchläufe nicht im voraus bekannt ist — etwa beim Lesen eines Streams, beim Pollen einer Bedingung oder beim Verarbeiten einer Queue.
const queue = [1, 2, 3];
while (queue.length > 0) {
const item = queue.shift();
console.log('Bearbeite', item);
// ggf. weitere Items in queue.push(...) — Schleife läuft weiter
if (item === 2) queue.push(99);
}Bearbeite 1
Bearbeite 2
Bearbeite 3
Bearbeite 99Eine for-Schleife mit arr.length-Bedingung wäre hier irreführend, weil length sich während der Schleife ändert. while (queue.length > 0) macht das explizit.
Die do/while-Schleife — fußgesteuert
Bei do { ... } while (bed); läuft der Block mindestens einmal — die Bedingung wird erst nach dem Durchlauf geprüft.
let eingabe;
let versuch = 0;
// Simulierte Eingaben — in echtem Code käme das z.B. von prompt()
const stub = ['', '', 'antwort'];
do {
eingabe = stub[versuch];
versuch++;
console.log('Versuch', versuch, ':', JSON.stringify(eingabe));
} while (eingabe === '');
console.log('Erhalten:', eingabe);Versuch 1 : ""
Versuch 2 : ""
Versuch 3 : "antwort"
Erhalten: antwortdo/while ist in der Praxis selten — der Anwendungsfall „mindestens ein Durchlauf, dann re-checken" ist nicht so häufig. Wenn er auftritt, ist do/while aber die direkteste Form. Wichtig: das Semikolon nach while(...) ist Pflicht.
Endlos-Schleifen — vermeiden oder bewusst nutzen
Eine Schleife mit konstant truthy Bedingung läuft endlos. In Node oder Browser blockiert das den Event-Loop — die Page friert ein, der Tab muss beendet werden.
// BUG: Schleife endet nie, weil i nie inkrementiert
let i = 0;
while (i < 10) {
console.log(i);
// i++ vergessen → Endlos
}
// BUG: Bedingung kann nie falsy werden
for (let n = 1; n > 0; n++) {
// n wird positiv und immer größer — niemals <= 0
}
// außer am MAX_SAFE_INTEGER-Übergang — nach 9 Billiarden IterationenBewusste Endlos-Schleifen sind in Worker- oder Daemon-Code legitim — etwa eine Event-Pump in einem Worker-Thread. Voraussetzung: ein zuverlässiger Exit-Pfad (break, return, throw).
function ersteFreiePort(start) {
for (let port = start; ; port++) {
if (port > 65535) throw new Error('Kein Port frei');
if (istFrei(port)) return port;
}
}
function istFrei(port) { return port === 8085; } // Demo-Stub
console.log(ersteFreiePort(8080)); // 80858085Die Closure-Falle mit var
Vor ES2015 — also vor let — war die berühmte „alle Callbacks loggen denselben Wert"-Falle Standard.
// Pre-ES2015 — alle Callbacks teilen dasselbe i
const callbacks = [];
for (var i = 0; i < 3; i++) {
callbacks.push(() => console.log(i));
}
callbacks.forEach(cb => cb());
// → 3, 3, 3 — weil i nach der Schleife 3 ist3
3
3Mit let ist das gelöst — jeder Durchlauf bekommt eine neue Bindung:
const callbacks = [];
for (let i = 0; i < 3; i++) {
callbacks.push(() => console.log(i));
}
callbacks.forEach(cb => cb());
// → 0, 1, 2 — jeder Callback hat sein eigenes i0
1
2Das ist einer der pragmatischsten Gründe, var heute komplett zu vermeiden — die Closure-Semantik in Schleifen ist mit let einfach „richtig".
break und continue — Kurzform
Schleifen werden mit break vorzeitig verlassen und mit continue zum nächsten Durchlauf gesprungen. Detail-Behandlung im Artikel zu Labels — hier nur die Grundform.
// break: Schleife ganz verlassen
for (let i = 0; i < 10; i++) {
if (i === 3) break;
console.log('A', i);
}
// → A 0, A 1, A 2
// continue: zum nächsten Durchlauf springen
for (let i = 0; i < 5; i++) {
if (i % 2 === 0) continue;
console.log('B', i);
}
// → B 1, B 3A 0
A 1
A 2
B 1
B 3continue in for-Schleifen springt zum Update-Teil — nicht zur Bedingung direkt. Daher bleiben Zähler-Schleifen mit continue korrekt.
Wann welche Schleife?
Faustregeln:
formit Zähler: bekannte Anzahl, Index-Zugriff nötig, Two-Pointer-Pattern, performance-kritisch.for...of: iterierbare Sammlung (Array, String, Map, Set, Iterable), kein Index nötig — sieht eigener Artikel.- Array-Methoden (
map,filter,reduce,forEach): wenn das Ergebnis ein neues Array oder ein aggregierter Wert ist. while: Anzahl der Durchläufe nicht im voraus bekannt, Bedingung ändert sich durch Side-Effects.do/while: mindestens ein Durchlauf garantiert nötig, danach Bedingungs-Check.
// Klassisch for: Index nötig
const arr = ['a', 'b', 'c'];
for (let i = 0; i < arr.length; i++) {
console.log(`[${i}] ${arr[i]}`);
}
// Modern for...of: Wert genügt
for (const c of arr) {
console.log(c);
}
// Array-Methode: neues Array
const groß = arr.map(c => c.toUpperCase());
console.log(groß);[0] a
[1] b
[2] c
a
b
c
[ 'A', 'B', 'C' ]Performance — meistens egal
Die klassische for-Schleife mit lokalem Index ist üblicherweise die schnellste Schleifen-Form, weil die Engine den Code aggressiv optimieren kann. forEach ist marginal langsamer (Function-Call-Overhead pro Element). for...of liegt dazwischen.
Praktisch ist der Unterschied bei modernen Engines im einstelligen Prozent-Bereich und nur bei Hunderttausenden Iterationen messbar. Wer also Lesbarkeit zugunsten von 3 % Performance opfert, optimiert am falschen Ende.
Echte Performance-Tipps liegen woanders: Allokationen in der Schleife reduzieren (Array nicht neu erzeugen), DOM-Zugriffe bündeln, Funktion-Inlining vermeiden bei monomorphen Hot-Paths. Schleifen-Form ist sekundär.
Interessantes
let in for-Init: jeder Durchlauf eine neue Bindung
Das ist eine Sonder-Regel der Sprache, nicht eine Konsequenz allgemeiner Scope-Regeln. for (let i = 0; ...; i++) erzeugt pro Durchlauf eine neue Closure-Bindung von i — daher funktionieren Callbacks innerhalb der Schleife wie erwartet. var i erzeugt nur eine einzige Bindung im umgebenden Function-Scope.
for-Header darf leer sein: for (;;) ist gültig
Alle drei Teile sind optional, die Semikolons aber nicht. for (;;) ist die kanonische Endlos-Schleife — kürzer als while (true) und in vielen Style-Guides bevorzugt, weil sie die Absicht klarer signalisiert.
Komma-Operator im for-Header — mehrere Variablen, ein Header
for (let i = 0, j = 10; ...; i++, j--) nutzt den Komma-Operator. Das ist einer der wenigen Stellen, an denen er sinnvoll ist. Wichtig: die Init-Variablen müssen denselben let- bzw. const-Modus teilen — gemischte Deklarationen sind nicht möglich.
do/while braucht Semikolon nach der Bedingung
do { ... } while (bed); — das abschließende Semikolon ist Teil der Syntax, nicht eine ASI-Konvention. Ohne Semikolon ist es zwar oft noch parsbar, aber manche Tools (insbesondere Minifier) hatten historisch Schwierigkeiten.
while (true) vs. for (;;) — keine Performance-Differenz
Beide werden von modernen Engines identisch optimiert. Die Wahl ist rein stilistisch. Manche Style-Guides bevorzugen for (;;) als „bewusste Endlos-Schleife", andere while (true) als „lesbarer".
continue in for springt zum Update, nicht zur Bedingung
Wichtiges Detail: for (let i = 0; i < 10; i++) { if (...) continue; ... } führt nach continue das i++ aus, dann die Bedingung. In while springt continue direkt zur Bedingung — daher muss man den Counter dort manuell vorher inkrementieren.
Schleifen-Init mit const: für const-Iteration nicht möglich
for (const i = 0; i < 3; i++) wirft TypeError: Assignment to constant variable beim ersten i++. const im for-Init ist nur sinnvoll, wenn der Update-Teil leer bleibt und die Variable im Body nicht reassigned wird — selten der Fall in Zähler-Schleifen. const funktioniert dagegen einwandfrei in for...of und for...in.
Endlos-Schleifen blockieren den Event-Loop in Browser und Node
JavaScript ist single-threaded — eine Endlos-Schleife im Main-Thread blockiert alles: UI, Timer, Promise-Callbacks. Browser zeigen nach einigen Sekunden „Skript reagiert nicht" und bieten Tab-Schließen an. Worker-Threads können dagegen sicher in Endlos-Loops sitzen, weil sie isoliert laufen.