Wenige Sprach-Features sorgen für so viele Memes wie JavaScripts Coercion — die automatische Typ-Konvertierung, die '5' + 3 zu '53' macht und '5' - 3 zu 2. Hinter den scheinbar willkürlichen Ergebnissen stehen vier präzise Algorithmen aus der ECMAScript-Spezifikation: ToBoolean, ToNumber, ToString und ToPrimitive. Wer sie kennt, kann jedes Coercion-Ergebnis vorhersagen — und besser noch: bewusst entscheiden, ob er sie nutzen will. In modernem Code ist die Antwort fast immer explizite Konvertierung mit Number(), String(), Boolean(), weil sie die Intention dokumentiert. Implizite Coercion bleibt für Vergleiche mit == und für die elegante Boolean-Verkürzung mit !! relevant — alles andere ist heute eine Quelle für Bugs.
Coercion: automatische Typ-Konvertierung
JavaScript ist eine dynamisch typisierte Sprache mit schwacher Typisierung: Werte haben Typen, aber Operatoren erzwingen ihre Operanden notfalls in den passenden Typ. Diese erzwungene Konvertierung heißt Type Coercion. Sie passiert an mehreren Stellen:
- bei Operatoren:
+,-,*,/,==,<,>,! - bei Kontroll-Strukturen:
if (x),while (x),x && y - bei Built-in-Funktionen:
Array(n),parseInt,String.prototype.repeat
Die ECMAScript-Spezifikation kennt vier zentrale Konvertierungs-Algorithmen, die intern aufgerufen werden: ToBoolean, ToNumber, ToString und ToPrimitive. Jeder hat klare Regeln — und genau diese Regeln zu verstehen ist der Schlüssel, um Coercion zu beherrschen statt zu fürchten.
Implizit vs. explizit — der zentrale Unterschied
Die zwei Wege liefern oft dasselbe Ergebnis, sind aber für die Code-Lesbarkeit Welten auseinander. Implizit versteckt die Konvertierung hinter einem Operator-Aufruf; explizit macht sie zur Top-Level-Operation.
const eingabe = '5';
// implizit (Coercion)
const a = eingabe + 3; // '53' — String-Konkatenation
const b = eingabe - 3; // 2 — Number-Coercion
const c = +eingabe; // 5 — Unary-Plus konvertiert
const d = !!eingabe; // true — doppelte Negation
// explizit (Conversion)
const e = Number(eingabe) + 3; // 8
const f = String(eingabe + 3); // '8' — falls eingabe eine Zahl wäre
const g = Number(eingabe); // 5
const h = Boolean(eingabe); // true
console.log(a, b, c, d, e, g, h);53 2 5 true 8 5 trueDie explizite Form dokumentiert die Absicht. Wer in einem Code-Review +x sieht, muss erst überlegen, was passiert; Number(x) erklärt sich selbst. Die einzigen idiomatischen Ausnahmen sind !!x für Boolean-Verkürzung und `${x}` (Template Literal) für String-Konvertierung — beide sind so weit etabliert, dass sie keine Erklärung mehr brauchen.
ToBoolean: die acht Falsy-Werte
Der einfachste der vier Algorithmen. JavaScript kennt genau acht Werte, die zu false konvertieren — alles andere ist true:
| Falsy-Wert | Beschreibung |
|---|---|
false | der Boolean-Wert selbst |
0 | Null als Number |
-0 | negative Null |
0n | Null als BigInt |
'' | leerer String |
null | bewusste Abwesenheit |
undefined | nicht-initialisierter Wert |
NaN | „keine Zahl" |
Alles andere ist truthy — auch das oft missverstandene leere Object {}, das leere Array [] und der String '0' (nicht-leer ist truthy, der Inhalt egal).
console.log(Boolean(false)); // false
console.log(Boolean(0)); // false
console.log(Boolean('')); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false
console.log(Boolean({})); // true (!)
console.log(Boolean([])); // true (!)
console.log(Boolean('0')); // true (!)
console.log(Boolean('false')); // true (!)
console.log(Boolean(-1)); // true
// !! ist die idiomatische Verkürzung
console.log(!!''); // false
console.log(!!1); // truefalse
false
false
false
false
false
true
true
true
true
true
false
trueToNumber: viele Wege, ein Ergebnis
Der ToNumber-Algorithmus konvertiert beliebige Werte in eine Number. Mehrere JavaScript-Konstrukte rufen ihn auf — und das Ergebnis variiert subtil zwischen ihnen, vor allem bei Strings.
// Number(x) — nutzt den vollen ToNumber-Algorithmus
console.log(Number('')); // 0 (leerer String → 0)
console.log(Number(' ')); // 0 (Whitespace gestrippt)
console.log(Number('123')); // 123
console.log(Number('123abc')); // NaN (nicht-numerische Reste)
console.log(Number('0x1F')); // 31 (Hex erkannt)
console.log(Number('1e3')); // 1000 (Exponential)
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
console.log(Number(true)); // 1
console.log(Number(false)); // 0
console.log(Number([])); // 0 (leeres Array → '' → 0)
console.log(Number([42])); // 42 (1-Element-Array → '42' → 42)
console.log(Number([1, 2])); // NaN (Multi-Element → '1,2' → NaN)
console.log(Number({})); // NaN
// parseInt(x, radix) — nimmt führende Ziffern, ignoriert Rest
console.log(parseInt('123abc')); // 123 (anders als Number!)
console.log(parseInt('10', 2)); // 2 (binär)
console.log(parseInt(' 42 ', 10)); // 42
// parseFloat — wie parseInt, aber für Floats
console.log(parseFloat('3.14xyz')); // 3.14
// Unary + — exakt wie Number()
console.log(+'42'); // 42
console.log(+true); // 10
0
123
NaN
31
1000
0
NaN
1
0
0
42
NaN
NaN
123
2
42
3.14
42
1Wichtige Unterschiede:
Number('123abc')istNaN,parseInt('123abc')ist123.Numberverlangt einen vollständig numerischen String,parseIntist permissiv.Number('')ist0,parseInt('')istNaN. Form-Inputs mit leerem Feld werden beiNumber()zur stillen0— eine klassische Validation-Falle.parseIntohne Radix ist gefährlich:parseInt('010', 10)ist eindeutig10, aberparseInt('010')war vor ES58(Octal-Interpretation). Heute ist es zwar10, aber den Radix-Parameter zu setzen ist trotzdem sauberer Stil.
ToString: drei Wege, leichte Unterschiede
Drei Konstrukte triggern String-Konvertierung: String(x), x.toString() und ein Template Literal `${x}`. Die wichtigste Unterscheidung: String(x) funktioniert für null und undefined, x.toString() wirft dort einen TypeError.
console.log(String(undefined)); // 'undefined'
console.log(String(null)); // 'null'
console.log(String(true)); // 'true'
console.log(String(42)); // '42'
console.log(String(NaN)); // 'NaN'
console.log(String(Infinity)); // 'Infinity'
console.log(String(42n)); // '42'
console.log(String(Symbol('x'))); // 'Symbol(x)'
console.log(String([])); // '' (!)
console.log(String([1, 2, 3])); // '1,2,3'
console.log(String({})); // '[object Object]'
console.log(String({ a: 1 })); // '[object Object]'
// x.toString() — wirft bei null/undefined
try {
null.toString();
} catch (err) {
console.log(err.constructor.name); // 'TypeError'
}
// Template Literal — konvertiert wie String(), aber wirft bei Symbol!
console.log(`${42}`); // '42'
try {
console.log(`${Symbol('x')}`);
} catch (err) {
console.log(err.constructor.name); // 'TypeError'
}undefined
null
true
42
NaN
Infinity
42
Symbol(x)
1,2,3
[object Object]
[object Object]
TypeError
42
TypeErrorString([]) als leerer String ist eine Quelle vieler Coercion-Memes — das ist die Wurzel von [] + [] === '' und [] + {} === '[object Object]'.
ToPrimitive: Object → Primitive
Wann immer ein Operator einen Primitive-Wert braucht und ein Object bekommt, ruft die Engine intern ToPrimitive(input, hint) auf. Der Algorithmus probiert in einer festen Reihenfolge:
- Hat das Object eine
[Symbol.toPrimitive](hint)-Methode? Dann nutze sie. - Sonst, je nach
hint:'string'→ ersttoString(), dannvalueOf()'number'(Default) → erstvalueOf(), danntoString()
- Wirft
TypeError, wenn keine Methode einen Primitive zurückgibt.
Symbol.toPrimitive ist seit ES6 die saubere Möglichkeit, Custom-Coercion für eigene Klassen zu definieren — etwa für Date-ähnliche Objects oder Money-Wrapper.
class Geld {
constructor(betrag, waehrung) {
this.betrag = betrag;
this.waehrung = waehrung;
}
[Symbol.toPrimitive](hint) {
if (hint === 'number') return this.betrag;
if (hint === 'string') return `${this.betrag.toFixed(2)} ${this.waehrung}`;
// hint === 'default' — meist String oder Number-Verhalten
return `${this.betrag} ${this.waehrung}`;
}
}
const preis = new Geld(19.99, 'EUR');
console.log(+preis); // 19.99 (hint: 'number')
console.log(`${preis}`); // '19.99 EUR' (hint: 'string')
console.log(preis + ''); // '19.99 EUR' (hint: 'default')
console.log(preis + 1); // '19.99 EUR1' (hint: 'default' → String)
console.log(preis * 2); // 39.98 (hint: 'number')
// Date hat valueOf() für Number-Coercion: gibt Timestamp zurück
const jetzt = new Date();
console.log(+jetzt); // z. B. 1746662400000 — Timestamp
console.log(`${jetzt}`); // 'Fri May 08 2026 ...'19.99
19.99 EUR
19.99 EUR
19.99 EUR1
39.98== vs. === — die Coercion-Regeln
=== (strict equality) macht keine Coercion — Operanden mit unterschiedlichem Typ sind sofort false. == (loose equality) führt einen mehrstufigen Algorithmus aus, der bei Typ-Differenz konvertiert. Die Regeln sind so subtil, dass sie eine eigene Tabelle verdienen:
| Vergleich | Ergebnis | Begründung |
|---|---|---|
null == undefined | true | Sonderregel — beide gelten als „leer" |
null == 0 | false | KEINE Number-Coercion bei null |
null == false | false | wie oben |
undefined == 0 | false | analog |
'5' == 5 | true | String → Number |
0 == '' | true | beide → 0 |
0 == '0' | true | String → 0 |
0 == false | true | Boolean → Number |
'' == false | true | beide → 0 |
[1] == 1 | true | Array → '1' → 1 |
[1, 2] == '1,2' | true | Array → String |
NaN == NaN | false | NaN ist mit nichts gleich |
{} == {} | false | Reference-Vergleich (verschiedene Objects) |
// null/undefined — gegenseitig gleich, sonst nichts
console.log(null == undefined); // true
console.log(null == null); // true
console.log(null == 0); // false (KEINE Coercion)
console.log(undefined == 0); // false
// String/Number — String wird zur Zahl
console.log('5' == 5); // true
console.log('0' == 0); // true
console.log('' == 0); // true
// Boolean → Number, dann Vergleich
console.log(true == 1); // true
console.log(true == '1'); // true ('1' → 1, true → 1)
console.log(false == ''); // true (beide → 0)
// Object → ToPrimitive
console.log([1] == 1); // true ([1] → '1' → 1)
console.log([1, 2] == '1,2'); // true
// Best Practice: einziger sinnvoller == -Einsatz
function istLeer(x) {
return x == null; // fängt null UND undefined
}
console.log(istLeer(null)); // true
console.log(istLeer(undefined)); // true
console.log(istLeer(0)); // falsetrue
true
false
false
true
true
true
true
true
true
true
true
true
falseKlassische Fallen: + ist polymorph
Der +-Operator ist der einzige arithmetische Operator, der zwei Bedeutungen hat: Zahl-Addition und String-Konkatenation. Sobald einer der Operanden ein String ist, wird der andere zu String konvertiert und die zwei Strings werden verkettet. Alle anderen Operatoren (-, *, /, %) sind eindeutig Number-only — sie konvertieren immer beide Seiten zu Number.
// + ist polymorph
console.log('5' + 3); // '53' — String-Konkatenation
console.log(3 + '5'); // '35' — egal welche Seite String ist
console.log(1 + 2 + '3'); // '33' — links nach rechts: 3 + '3' = '33'
console.log('1' + 2 + 3); // '123' — '12' + 3 = '123'
// - * / % sind Number-only
console.log('5' - 3); // 2 — '5' → 5
console.log('10' * '2'); // 20 — beide → Number
console.log('15' / 3); // 5
console.log('10' % 3); // 1
// berühmte Curiosa
console.log([] + []); // '' — beide → ''
console.log([] + {}); // '[object Object]'
console.log({} + []); // '[object Object]' (in Konsolen unterschiedlich!)
console.log(true + 1); // 2 — true → 1
console.log(true + 'x'); // 'truex' — true → 'true'
console.log([1] + [2]); // '12' — '1' + '2'
console.log([1] - [2]); // -1 — 1 - 2
// klassische Form-Falle: + statt Number()
const eingabe = '7';
const summe = eingabe + 3; // '73' — Bug
const summeKorrekt = +eingabe + 3; // 10
console.log(summe, summeKorrekt);53
35
33
123
2
20
5
1
[object Object]
[object Object]
2
truex
12
-1
73 10Best Practice: explizit konvertieren
In modernem Code ist die klare Regel: immer explizit konvertieren, außer bei zwei etablierten Idiomen. Das macht Reviews einfacher, fängt Bugs an der Eingabe statt tief in der Pipeline und harmoniert mit Lintern und TypeScript.
| Methode | Beispiel | Wann nutzen? |
|---|---|---|
Number(x) | Number('42') | strikte numerische Konvertierung |
parseInt(x, 10) | parseInt('42px', 10) | permissiv: führende Ziffern aus String |
parseFloat(x) | parseFloat('3.14em') | Float-Variante von parseInt |
+x (unary plus) | +'42' | Kurzform — nur in trivialen Fällen |
String(x) | String(42) | sicher auch für null/undefined |
`${x}` | `${42}` | im Template-Kontext idiomatisch |
x.toString() | (42).toString(2) | wenn Radix nötig (Binär, Hex) |
Boolean(x) | Boolean(value) | explizite Boolean-Konvertierung |
!!x | !!value | etablierte Kurzform — auch akzeptiert |
// gut: explizite Conversion an der Eingabe-Grenze
function berechneSumme(eingaben) {
return eingaben
.map(s => Number(s)) // explizite Konvertierung
.filter(n => Number.isFinite(n)) // ungültige raus
.reduce((acc, n) => acc + n, 0);
}
console.log(berechneSumme(['10', '20', 'abc', '30'])); // 60
// schlecht: implizite Coercion in Arithmetik
function berechneSummeImplizit(eingaben) {
return eingaben.reduce((acc, n) => acc + n, 0);
}
console.log(berechneSummeImplizit(['10', '20', '30'])); // '0102030' — Bug!60
0102030Praxis: Form-Input-Verarbeitung
Der häufigste reale Coercion-Use-Case ist die Verarbeitung von Form-Inputs. DOM-Elemente liefern Werte immer als String — auch <input type="number">. Wer rechnen will, muss konvertieren — und zwar mit Validation, weil leere und ungültige Eingaben sonst still zu 0 oder NaN werden.
function parseFormZahl(rohwert, fallback = null) {
// 1. leere Eingabe explizit behandeln
if (rohwert == null || rohwert.trim() === '') {
return fallback;
}
// 2. Number() statt parseInt — strikt, fängt '12abc'
const zahl = Number(rohwert);
// 3. NaN-Check separat — Number('abc') ist NaN, nicht null
if (!Number.isFinite(zahl)) {
return fallback;
}
return zahl;
}
console.log(parseFormZahl('42')); // 42
console.log(parseFormZahl(' 42 ')); // 42 (Whitespace gestrippt)
console.log(parseFormZahl('')); // null
console.log(parseFormZahl(' ')); // null
console.log(parseFormZahl('abc')); // null
console.log(parseFormZahl('12abc')); // null (anders als parseInt!)
console.log(parseFormZahl(null)); // null
// Anti-Pattern: implizite Coercion
// const alter = +event.target.value; // '' → 0 — falsch!
// const alter = parseInt(event.target.value); // '12abc' → 12 — auch falsch!42
42
null
null
null
null
nullDrei Lehren aus diesem Beispiel:
- Eingabe immer explizit prüfen (
null, leerer String, Whitespace). Number()stattparseInt, wenn der gesamte String numerisch sein muss.Number.isFinitestatt!isNaN— fängt zusätzlichInfinityund ist robuster.
Coercion-Fallen
[] + {} ist '[object Object]'
Beide Operanden werden zu Strings: [] wird '', {} wird '[object Object]', dann Konkatenation. Das berühmteste Coercion-Meme der Sprache — und ein guter Grund, + nicht für Object-Operationen zu nutzen.
'5' + 3 ist '53', aber '5' - 3 ist 2
+ ist der einzige polymorphe Operator (String oder Number, je nach Operanden). -, *, / sind Number-only und konvertieren beide Seiten. Wer Form-Inputs addieren will, muss vorher explizit zu Number konvertieren.
== hat ~10 Sonderfälle, viele kontraintuitiv
null == undefined ist true, aber null == 0 ist false. 0 == '' ist true, aber null == false ist false. Die Regeln sind so wirr, dass Linter === erzwingen — mit der einzigen Ausnahme x == null für die null/undefined-Sammelprüfung.
parseInt('10', 8) ist 8 — Radix immer angeben
parseInt akzeptiert einen optionalen Radix. Vor ES5 wurde leading-0 als Octal interpretiert (parseInt('010') war 8). Heute ist es 10, aber den Radix explizit zu setzen verhindert Verwirrung in altem Code und in Reviews.
Number('') ist 0 — die Form-Input-Falle
Ein leeres Input-Feld liefert einen leeren String; Number('') ist 0. Wer ungeprüft konvertiert, addiert plötzlich Nullen, wo eigentlich „kein Wert" gemeint war. Lösung: vor Number() auf leeren String prüfen.
!!x und Boolean(x) sind exakt äquivalent
Beide rufen den ToBoolean-Algorithmus. !!x ist die etablierte Kurzform; Boolean(x) ist expliziter. Stilfrage: in Bedingungen meist !!, in API-Returns lieber Boolean().
Symbol.toPrimitive überschreibt valueOf und toString
Wenn ein Object eine [Symbol.toPrimitive]-Methode hat, wird sie für ALLE Coercion-Pfade aufgerufen — valueOf und toString werden ignoriert. Das ist die moderne, kontrollierte Methode für Custom-Coercion in eigenen Klassen.
+(new Date()) gibt den Timestamp zurück
Date.prototype.valueOf() liefert die Millisekunden seit Epoch. Mit Unary-Plus wird das ausgenutzt: +new Date() ist eine kompakte Timestamp-Erzeugung. Eleganter und expliziter ist Date.now() oder new Date().getTime().
Symbol-Werte werfen bei Number()-Konvertierung
Number(Symbol()) wirft einen TypeError — Symbols haben keine numerische Repräsentation. Auch implizite Number-Coercion (+sym, sym * 2) wirft. Bei String-Coercion ist nur String(sym) sicher; Template Literals werfen ebenfalls.
Weiterführende Ressourcen
Externe Quellen
- Type coercion – MDN Glossary
- Equality comparisons and sameness – MDN
- Equality (==) – MDN
- Symbol.toPrimitive – MDN
- ToPrimitive – ECMAScript Spec
- Abstract Equality Comparison – ECMAScript Spec