for...of und for...in sehen syntaktisch fast identisch aus, machen aber grundverschiedene Dinge. for...of (ES2015) iteriert über Werte einer iterierbaren Sammlung — Arrays, Strings, Maps, Sets, NodeLists, Generators. for...in (seit ES1) enumeriert die Property-Keys eines Objekts, inklusive geerbter Properties. Wer sie verwechselt, bekommt entweder das ganze Prototype-Geschwafel im Array geliefert oder versucht, ein Objekt zu iterieren und bekommt undefined. Dieser Artikel zeigt beide Mechanismen klar getrennt und nennt eindeutige Faustregeln.
for...of — Werte aus Iterables
for (const x of iterable) greift auf das Iterator-Protokoll zu: das Objekt muss eine [Symbol.iterator]()-Methode haben, die einen Iterator zurückgibt. Built-in iterable sind Array, String, Map, Set, TypedArray, NodeList, arguments und alle Generator-Resultate.
// Array
for (const x of [10, 20, 30]) console.log(x);
// String — pro Grapheme/Codepoint
for (const c of 'abc') console.log(c);
// Set
for (const v of new Set([1, 2, 2, 3])) console.log(v);
// Map — Tupel [key, value]
const m = new Map([['a', 1], ['b', 2]]);
for (const [k, v] of m) console.log(k, '→', v);10
20
30
a
b
c
1
2
3
a → 1
b → 2Wichtig: for...of ignoriert geerbte Properties komplett — es geht direkt über die Element-Werte, ohne irgendwelchen Property-Lookup-Mechanismus.
for...in — Property-Keys eines Objekts
for (const key in obj) enumeriert die enumerable string-keyed Properties eines Objekts, inklusive der geerbten aus der Prototype-Chain. Es liefert immer Strings — auch bei Array-Indizes.
const user = { name: 'Anna', alter: 30, ort: 'Berlin' };
for (const key in user) {
console.log(key, '→', user[key]);
}name → Anna
alter → 30
ort → BerlinSymbol-Keys werden nicht enumeriert. Non-enumerable Properties (per Object.defineProperty({ enumerable: false })) auch nicht. Aber: Properties des Prototypen schon.
for...in auf Arrays — fast immer falsch
Arrays sind Objekte, deren Indizes als String-Properties vorliegen. for...in enumeriert sie — aber mit drei Problemen: Reihenfolge nicht garantiert, Keys als Strings, geerbte Properties miterfasst.
const arr = ['a', 'b', 'c'];
arr.zusatz = 'oh nein'; // beliebige Property auf Array
// for-in iteriert auch über die zusätzliche Property
for (const k in arr) {
console.log(k, typeof k, '→', arr[k]);
}
// Keys kommen als String, nicht als Number!0 string → a
1 string → b
2 string → c
zusatz string → oh neinVerschärft wird das durch alte Codebasen, die Array.prototype erweitert haben (Anti-Pattern, aber kommt vor). Mit for...in tauchen diese geerbten Methoden plötzlich als Keys auf.
Faustregel: for...in niemals auf Arrays. Für Werte for...of oder arr.forEach, für Indizes for (let i = 0; i < arr.length; i++) oder arr.entries() in for...of.
for...of auf Plain-Objects — wirft TypeError
Plain-Objects sind nicht iterable. for...of schlägt mit TypeError fehl, weil Symbol.iterator fehlt.
const user = { name: 'Anna', alter: 30 };
try {
for (const x of user) console.log(x);
} catch (e) {
console.log('Fehler:', e.message);
}
// TypeError: user is not iterableFehler: user is not iterableDie idiomatische Lösung: das Objekt explizit in eine iterable Form bringen.
const user = { name: 'Anna', alter: 30 };
// Keys
for (const k of Object.keys(user)) console.log(k);
// Werte
for (const v of Object.values(user)) console.log(v);
// Beide als Tupel
for (const [k, v] of Object.entries(user)) {
console.log(`${k}: ${v}`);
}name
alter
Anna
30
name: Anna
alter: 30Object.entries ist seit ES2017 die idiomatische Form, ein Objekt zu iterieren. Sie erfasst nur eigene Properties (keine geerbten) und liefert die Keys als Strings, die Werte typgerecht.
Reihenfolge-Garantie
Bei for...of ist die Reihenfolge die der Iterable-Definition — Array-Reihenfolge, Insertion-Order in Map/Set, etc.
Bei for...in ist die Reihenfolge historisch nicht definiert gewesen. Seit ES2020 ist sie aber spezifiziert: zuerst Integer-artige String-Keys numerisch sortiert, dann andere String-Keys in Insertion-Order, dann Keys des Prototypen in derselben Logik.
const obj = {
'2': 'zwei',
'a': 'A',
'1': 'eins',
'b': 'B',
};
for (const k in obj) console.log(k);
// → '1', '2' (numerisch sortiert), dann 'a', 'b' (insertion order)1
2
a
bDiese Regel gilt auch für Object.keys und Object.entries. In sehr alten Engines (pre-ES2020) konnte die Reihenfolge anders sein — daher: in Code, der auf Reihenfolge angewiesen ist, lieber Map nutzen, die garantiert Insertion-Order liefert.
Index mit for...of — .entries() als Helfer
for...of liefert standardmäßig nur die Werte. Wer Index UND Wert braucht, nutzt arr.entries(), das einen Iterator von [index, value]-Tupeln liefert.
const arr = ['a', 'b', 'c'];
for (const [i, v] of arr.entries()) {
console.log(`[${i}] ${v}`);
}
// Alternative: forEach
arr.forEach((v, i) => console.log(`[${i}] ${v}`));[0] a
[1] b
[2] c
[0] a
[1] b
[2] cBeide Formen sind gleichwertig — for...of mit .entries() hat den Vorteil, dass break, continue und await darin funktionieren. forEach hat das nicht.
for...of auf Strings — Unicode-bewusst
Strings sind iterable. for...of iteriert über Codepoints, nicht über UTF-16-Code-Units. Das ist der entscheidende Vorteil gegenüber for (let i = 0; i < str.length; i++) { str[i] } — letzteres bricht bei Surrogate-Pairs (Emojis, manche CJK-Zeichen).
const text = 'a😀b';
console.log('length:', text.length); // 4 — UTF-16-Units
// Falsch: Index-basiert, zerlegt Emoji
for (let i = 0; i < text.length; i++) {
console.log(`[${i}] ${text[i]}`);
}
// Richtig: for-of, pro Codepoint
for (const c of text) {
console.log(c);
}length: 4
[0] a
[1] �
[2] �
[3] b
a
😀
bfor...of ist daher der korrekte Weg, einen String zeichen-weise zu durchgehen — die Index-Form liefert in vielen Sprachen schlicht falsche Resultate.
for await...of — Asynchrone Iteration
Seit ES2018 gibt es for await...of für AsyncIterables — Streams, Async Generators, Datenbank-Cursor. Innerhalb einer async-Funktion erlaubt es, sequenziell auf jeden Wert zu warten.
async function* zaehlen(n) {
for (let i = 1; i <= n; i++) {
await new Promise(r => setTimeout(r, 10));
yield i;
}
}
(async () => {
for await (const x of zaehlen(3)) {
console.log('Bekam', x);
}
})();Bekam 1
Bekam 2
Bekam 3Detail-Behandlung im Async-Iteration-Artikel. Hier nur die Erwähnung, dass for...of einen asynchronen Bruder hat.
break, continue und return in beiden
Sowohl for...of als auch for...in unterstützen break, continue und return. Bei for...of wird zusätzlich die .return()-Methode des Iterators aufgerufen — was bei Generators einen finally-Block ausführt.
function* gen() {
try {
yield 1;
yield 2;
yield 3;
} finally {
console.log('Cleanup im Generator');
}
}
for (const v of gen()) {
console.log('Wert:', v);
if (v === 2) break; // löst Cleanup aus
}Wert: 1
Wert: 2
Cleanup im GeneratorDas ist eine Eigenschaft, die arr.forEach nicht hat — forEach kann gar nicht mit break verlassen werden, und der Callback hat keinen Bezug zur „äußeren" Schleifen-Logik.
Faustregeln
| Wenn ich ... | nehme ich ... |
|---|---|
| Werte aus Array / String / Map / Set | for...of |
| Index UND Wert aus Array | for...of mit arr.entries() oder forEach |
| Property-Keys eines Plain-Objects | Object.keys + for...of |
| Property-Werte eines Plain-Objects | Object.values + for...of |
| Key+Wert eines Plain-Objects | Object.entries + for...of |
| Nur eigene String-Keys, auch geerbt enumerable | for...in (mit Object.hasOwn-Filter) |
| Async-Quelle (Stream, async generator) | for await...of |
| Eigene Index-Logik mit Two-Pointer | klassisches for |
for...in ist in modernem JS-Code selten die richtige Wahl. Sobald ich ein Plain-Object iteriere, nehme ich Object.entries/keys/values; sobald ich ein Array iteriere, for...of oder Array-Methoden. for...in bleibt nur für Sonder-Fälle, in denen ich explizit auch geerbte Properties brauche — und das ist sehr selten.
Häufige Stolperfallen
for-in liefert Keys als Strings — auch Array-Indizes
for (const k in [10, 20]) liefert k als '0', '1' — Strings, keine Numbers. Wer dann k + 1 rechnet, bekommt '01' statt 1. Der Klassiker. Lösung: gar nicht erst for...in auf Arrays — for...of mit entries() oder klassisches for.
for-in folgt der Prototype-Chain
Wer einem Plain-Object eine eigene Method-Property an Object.prototype hängt (Anti-Pattern, kommt aber in alten Codebasen vor), bekommt diese in jedem for...in-Lauf mit. Schutz: if (Object.hasOwn(obj, key)) als Filter innerhalb der Schleife — oder einfach Object.keys/entries nutzen, die diese Filterung schon machen.
for-of auf Plain-Object wirft TypeError
for (const x of { a: 1 }) ist nicht iterable. Die Falle: Anfänger erwarten, dass for...of universal funktioniert. Lösung: Object.entries, Object.keys, Object.values als Zwischen-Schritt.
for-in-Reihenfolge: numerische Keys zuerst
Seit ES2020 spezifiziert: integer-artige String-Keys numerisch sortiert ZUERST, dann andere Keys in Insertion-Order. Das heißt: { b: 1, '1': 2, a: 3 } wird als '1', 'b', 'a' enumeriert — wer Insertion-Order erwartet, wird überrascht. Map ist die saubere Alternative für reine Insertion-Order.
for-of mit await — sequenziell, nicht parallel
for (const url of urls) { await fetch(url); } läuft sequenziell. Wer Parallelität will: await Promise.all(urls.map(fetch)). Das ist keine Schwäche, sondern Absicht — for-of garantiert Reihenfolge.
for-in auf null/undefined wirft NICHT
Anders als for-of: for (const k in null) macht einfach nichts (null/undefined ist „leeres Property-Set"). Das ist eine subtile Inkonsistenz zwischen den beiden Formen. Sicherheit: vor for-in lieber Null-Check.
String-Index str[i] vs. for-of c — Surrogate-Pair-Problem
'😀'.length ist 2 (UTF-16 Surrogate-Pair). str[0] liefert nur die obere Hälfte. for (const c of '😀') liefert das ganze Emoji als c. Daher: für Unicode-korrektes Zeichen-Iterieren immer for...of, nicht Index-Zugriff.
forEach lässt sich nicht mit break verlassen
Häufige Konfusion: arr.forEach(v => { if (...) break; }) ist ein SyntaxError, weil break nicht in Callback-Funktionen passt. Wenn vorzeitig verlassen werden muss: for...of mit break nehmen — oder arr.some()/arr.every() als Abbruch-fähige Alternative.
Weiterführende Ressourcen
Externe Quellen
- for...of – MDN
- for...in – MDN
- for await...of – MDN
- Iteration protocols – MDN
- Property Enumeration Order – ECMAScript Spec