Die switch-Anweisung ist die Mehrweg-Verzweigung von JavaScript — eine Folge von case-Labels, gegen die der Diskriminant-Ausdruck per strict equality (===) verglichen wird. Was switch von vielen anderen Sprachen unterscheidet, ist das Fall-through-Verhalten: ohne break läuft die Ausführung in den nächsten case weiter. Das ist mal nützlich (geteilte Behandlung mehrerer Werte), häufiger aber eine Quelle von Bugs. Dieser Artikel zeigt die Mechanik, dokumentiert die typischen Fallen und stellt das Object-Lookup-Pattern vor, das in modernem Code oft die bessere Wahl ist.
Grundform
Eine switch-Anweisung evaluiert einmal den Ausdruck in den Klammern und vergleicht ihn der Reihe nach mit jedem case-Wert per ===. Bei Übereinstimmung startet die Ausführung dort und läuft weiter, bis ein break, ein return oder das Ende erreicht ist.
function wochentag(nr) {
switch (nr) {
case 0: return 'Sonntag';
case 1: return 'Montag';
case 2: return 'Dienstag';
case 3: return 'Mittwoch';
case 4: return 'Donnerstag';
case 5: return 'Freitag';
case 6: return 'Samstag';
default: return 'Unbekannt';
}
}
console.log(wochentag(3)); // 'Mittwoch'
console.log(wochentag(7)); // 'Unbekannt'Mittwoch
UnbekanntWeil hier jedes case mit return endet, gibt es kein Fall-through-Problem. Das ist das einfachste — und stilistisch sauberste — switch-Muster.
Vergleich per === — strict equality
Der case-Vergleich nutzt strict equality (===), nicht den lockeren ==-Vergleich. Das heißt: Typ-Coercion findet nicht statt. case '1': matched nicht auf switch (1).
const wert = '1';
switch (wert) {
case 1:
console.log('Number 1'); break;
case '1':
console.log('String "1"'); break;
default:
console.log('Nichts'); break;
}
// → 'String "1"' — keine Coercion!String "1"Das ist eine wichtige Eigenschaft, die in dynamisch getypten Codebasen oft übersehen wird — besonders bei Eingaben aus HTML-Forms, die als String kommen, aber Datenmodell-seitig als Number gedacht sind.
Fall-through — gewollt und ungewollt
Ohne break läuft die Ausführung in das nächste case weiter, ohne dessen Bedingung zu prüfen. Das ist Fall-through und der häufigste Switch-Bug.
function preis(zone) {
switch (zone) {
case 'A':
console.log('Zone A');
// break vergessen!
case 'B':
console.log('Zone B');
break;
case 'C':
console.log('Zone C');
break;
}
}
preis('A'); // 'Zone A' UND 'Zone B' — fall-through!Zone A
Zone BGewollt kann Fall-through bei Wert-Gruppen sein: mehrere case-Labels ohne Body zwischen ihnen führen zum gleichen Block.
function istWochenende(nr) {
switch (nr) {
case 0:
case 6:
return true;
default:
return false;
}
}
console.log(istWochenende(0)); // true
console.log(istWochenende(3)); // falsetrue
falseESLint hat dafür die Regel no-fallthrough. Sie warnt bei jedem fehlenden break, akzeptiert aber explizite // falls through-Kommentare als Opt-out für die Wert-Gruppen-Variante.
default-Position — überall erlaubt, am Ende üblich
Konventionell steht default am Ende, aber der Spec nach kann er an jeder Stelle stehen. Das Matching geht zuerst alle case-Labels durch; nur wenn keiner trifft, springt die Ausführung an den default-Punkt — und läuft von dort wieder mit Fall-through weiter.
function pruefe(x) {
switch (x) {
case 1:
console.log('eins'); break;
default:
console.log('default'); // läuft, weil 2 in keinem case
// kein break → fall-through in case 3
case 3:
console.log('drei'); break;
}
}
pruefe(2); // 'default' UND 'drei' — fall-through nach defaultdefault
dreiDas ist mehr Code-Golf-Wissen als Best-Practice. In sauberem Code: default immer am Ende, mit break oder return.
let/const im case — der Block-Scope-Stolperer
Alle case-Bodies teilen sich denselben Block — den switch-Block. Wer in einem case let oder const deklariert, hat die Variable im gesamten Switch-Scope, mit allen TDZ-Effekten.
function f(x) {
switch (x) {
case 1:
let name = 'eins'; // SyntaxError beim zweiten case?
return name;
case 2:
let name = 'zwei'; // SyntaxError: Identifier 'name' already declared
return name;
}
}Die Lösung: eigenen Block pro case in geschweiften Klammern. Das ist die einzige saubere Form für lokale Variablen pro Zweig.
function f(x) {
switch (x) {
case 1: {
const name = 'eins';
return name;
}
case 2: {
const name = 'zwei'; // ok — eigener Block
return name;
}
default:
return 'unbekannt';
}
}
console.log(f(1)); // 'eins'
console.log(f(2)); // 'zwei'eins
zweiKonvention in vielen Style-Guides: wenn ein case eigene Variablen braucht, dann immer mit {...}. Das vermeidet die Falle pauschal.
switch (true) — Bereiche statt fester Werte
Weil case per === vergleicht, lassen sich Bereichs-Tests nicht direkt ausdrücken — case x >= 18: wäre fast nie wahr (außer der Diskriminant ist exakt true). Es gibt einen Trick: switch (true) mit Ausdrücken in den case-Labels.
function altersgruppe(alter) {
switch (true) {
case alter < 6: return 'Kindergarten';
case alter < 14: return 'Schulkind';
case alter < 18: return 'Jugendlich';
default: return 'Erwachsen';
}
}
console.log(altersgruppe(10)); // 'Schulkind'
console.log(altersgruppe(17)); // 'Jugendlich'
console.log(altersgruppe(40)); // 'Erwachsen'Schulkind
Jugendlich
ErwachsenFunktioniert, ist aber kontrovers: viele Style-Guides lehnen das Pattern ab, weil es die Semantik von switch (diskreter Werte-Vergleich) zweckentfremdet. Eine if/else if-Kette wäre der lesbarere Ausdruck der gleichen Logik.
Object-Lookup als modernes Pattern
Sobald die Cases nur Wert-Mappings sind — kein komplexer Code, keine Nebeneffekte — ist ein Object-Lookup fast immer besser als switch.
// switch-Variante
function farbeSwitch(level) {
switch (level) {
case 'info': return 'blue';
case 'warn': return 'orange';
case 'error': return 'red';
default: return 'gray';
}
}
// Object-Lookup
const farben = {
info: 'blue',
warn: 'orange',
error: 'red',
};
const farbeLookup = level => farben[level] ?? 'gray';
console.log(farbeLookup('warn')); // 'orange'
console.log(farbeLookup('unknown')); // 'gray'orange
grayBei Funktions-Mappings noch klarer:
const handler = {
click: e => console.log('click @', e.x, e.y),
scroll: e => console.log('scroll @', e.y),
keydown: e => console.log('key', e.key),
};
function dispatch(typ, event) {
const h = handler[typ];
h ? h(event) : console.log('Unbekannter Typ:', typ);
}
dispatch('click', { x: 10, y: 20 });
dispatch('unknown', {});click @ 10 20
Unbekannter Typ: unknownVorteile: keine Fall-through-Falle, keine break-Pflicht, einfaches Hinzufügen neuer Fälle, Mapping als Daten (kann von außen kommen). Nachteil: nur für diskrete Werte und direktes Mapping geeignet — komplexe Logik pro Fall braucht wieder einen switch oder if.
NaN im switch — niemals erreicht
NaN === NaN ist false. Daher matched kein case NaN: jemals. Wer auf NaN prüfen will, muss Number.isNaN() mit einem if benutzen — switch hilft hier nicht.
const x = NaN;
switch (x) {
case NaN:
console.log('NaN'); break; // wird nie erreicht
default:
console.log('Default');
}
// → 'Default'
// Korrekt:
if (Number.isNaN(x)) {
console.log('ist NaN');
}Default
ist NaNGleiches Detail: case -0: und switch (0) matchen (weil === zwischen -0 und 0 true ist). Nur Object.is würde sie unterscheiden — was switch nicht nutzt.
Exhaustiveness — alle Fälle abdecken
In TypeScript ist das Pattern, einen default: const _: never = x; einzufügen, um Compile-Zeit-Exhaustiveness zu erzwingen. In reinem JavaScript fehlt diese Hilfe — der Ersatz ist ein default, der explizit eine Exception wirft.
function farbe(level) {
switch (level) {
case 'info': return 'blue';
case 'warn': return 'orange';
case 'error': return 'red';
default:
throw new Error(`Unbekanntes Level: ${level}`);
}
}
console.log(farbe('warn')); // 'orange'
try {
console.log(farbe('bla')); // wirft
} catch (e) {
console.log('Fehler:', e.message);
}orange
Fehler: Unbekanntes Level: blaVorteil: ein unbedachter neuer Level-Wert, der irgendwo in den Code rutscht, wirft sofort und laut — statt still einen falschen Default zu liefern.
Performance — keine Sorge bei kleinen Switches
V8 und SpiderMonkey kompilieren switch-Statements je nach Größe als Sequenz von Vergleichen oder als Jump-Table (bei dichten Integer-Cases). Praktisch ist der Unterschied zu einer if/else if-Kette unmessbar bei wenigen Cases. Bei Hunderten von numerischen Cases hätte ein Object-Lookup wahrscheinlich einen marginal anderen Profil — aber das ist nie der Engpass realer Anwendungen.
Daher: die Wahl zwischen switch, if-Kette und Lookup ist eine Lesbarkeits-Frage, keine Performance-Frage.
Häufige Stolperfallen
fehlendes break — der absolute Klassiker
Vergessenes break ist Top-1-Bug-Quelle bei switch. ESLint's no-fallthrough warnt zuverlässig; explizit gewollte Fall-throughs markiert man mit dem Kommentar // falls through direkt vor dem nächsten case. Wer keine Tooling-Unterstützung hat: jedes case mit return oder break abschließen, ohne Ausnahme.
strict equality === — '1' matched NICHT auf 1
Der Vergleich ist ===, keine Coercion. Eingaben aus DOM-Forms (alle Strings) gegen Number-Konstanten zu switchen ist ein häufiger Bug. Lösung: vorher per Number(x) oder parseInt(x, 10) konvertieren — oder die case-Labels als Strings schreiben.
let/const ohne Block im case — Identifier already declared
case-Bodies teilen einen einzigen Block — den Switch-Block. Zwei let name-Deklarationen in verschiedenen Cases sind ein SyntaxError. Lösung: jeden Case mit eigenen Variablen in { ... } kapseln. Style-Guides wie Airbnb verlangen das standardmäßig.
case NaN matched nie, weil NaN === NaN false ist
Schon in der Sprache verankert: NaN ist mit nichts gleich, auch nicht mit sich selbst. Wer NaN erwartet, muss if (Number.isNaN(x)) vor dem switch benutzen — oder einen default-Zweig mit Number.isNaN-Prüfung.
default in der Mitte — fall-through funktioniert auch darüber
Spec-erlaubt, aber irreführend. Wenn default mitten im switch steht und kein break hat, läuft die Ausführung in den darunter stehenden Case. In sauberem Code: default immer am Ende, mit Terminator.
switch (obj) auf Objekte — fast immer ein Bug
Vergleich per === auf Object-Referenzen funktioniert nur, wenn man dieselbe Referenz im case nutzt. Das passiert praktisch nie sinnvoll — meist will man auf eine Property switchen (switch (obj.typ)). Linting fängt das nicht ab; bewusste Schreibweise hilft.
switch (true) — Pattern für Bereichs-Tests, aber kontrovers
switch (true) { case alter < 18: ... } funktioniert und wirkt manchmal kompakter als eine if-Kette. Viele Style-Guides verbieten es als Zweckentfremdung. Persönliche Empfehlung: lieber if/else if, weil dort die Bereichs-Semantik direkt sichtbar ist.
Object-Lookup ist nicht immer die bessere Wahl
Lookup-Pattern ist großartig für Werte-Mappings, aber schlecht für: komplexe Logik pro Fall, mehrere Statements, fall-through-Verhalten (Wert-Gruppen). Faustregel: wenn jeder Fall mehr als 1-2 Zeilen Code hat, switch oder ausgelagerte Handler-Funktion sind klarer.
Weiterführende Ressourcen
Externe Quellen
- switch – MDN
- The switch Statement – ECMAScript Spec
- no-fallthrough – ESLint
- Pattern Matching Proposal – TC39