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.

JavaScript implizit-vs-explizit.js
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);
Output
53 2 5 true 8 5 true

Die 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-WertBeschreibung
falseder Boolean-Wert selbst
0Null als Number
-0negative Null
0nNull als BigInt
''leerer String
nullbewusste Abwesenheit
undefinednicht-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).

JavaScript toboolean.js
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);  // true
Output
false
false
false
false
false
false
true
true
true
true
true
false
true

ToNumber: 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.

JavaScript tonumber-tabelle.js
// 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);  // 1
Output
0
0
123
NaN
31
1000
0
NaN
1
0
0
42
NaN
NaN
123
2
42
3.14
42
1

Wichtige Unterschiede:

  • Number('123abc') ist NaN, parseInt('123abc') ist 123. Number verlangt einen vollständig numerischen String, parseInt ist permissiv.
  • Number('') ist 0, parseInt('') ist NaN. Form-Inputs mit leerem Feld werden bei Number() zur stillen 0 — eine klassische Validation-Falle.
  • parseInt ohne Radix ist gefährlich: parseInt('010', 10) ist eindeutig 10, aber parseInt('010') war vor ES5 8 (Octal-Interpretation). Heute ist es zwar 10, 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.

JavaScript tostring-tabelle.js
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'
}
Output
undefined
null
true
42
NaN
Infinity
42
Symbol(x)

1,2,3
[object Object]
[object Object]
TypeError
42
TypeError

String([]) 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:

  1. Hat das Object eine [Symbol.toPrimitive](hint)-Methode? Dann nutze sie.
  2. Sonst, je nach hint:
    • 'string' → erst toString(), dann valueOf()
    • 'number' (Default) → erst valueOf(), dann toString()
  3. 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.

JavaScript symbol-toprimitive.js
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 ...'
Output
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:

VergleichErgebnisBegründung
null == undefinedtrueSonderregel — beide gelten als „leer"
null == 0falseKEINE Number-Coercion bei null
null == falsefalsewie oben
undefined == 0falseanalog
'5' == 5trueString → Number
0 == ''truebeide → 0
0 == '0'trueString → 0
0 == falsetrueBoolean → Number
'' == falsetruebeide → 0
[1] == 1trueArray → '1' → 1
[1, 2] == '1,2'trueArray → String
NaN == NaNfalseNaN ist mit nichts gleich
{} == {}falseReference-Vergleich (verschiedene Objects)
JavaScript loose-equality.js
// 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));         // false
Output
true
true
false
false
true
true
true
true
true
true
true
true
true
false

Klassische 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.

JavaScript polymorphes-plus.js
// + 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);
Output
53
35
33
123
2
20
5
1

[object Object]
[object Object]
2
truex
12
-1
73 10

Best 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.

MethodeBeispielWann 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!!valueetablierte Kurzform — auch akzeptiert
JavaScript explizit-konvertieren.js
// 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!
Output
60
0102030

Praxis: 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.

JavaScript form-validation.js
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!
Output
42
42
null
null
null
null
null

Drei Lehren aus diesem Beispiel:

  1. Eingabe immer explizit prüfen (null, leerer String, Whitespace).
  2. Number() statt parseInt, wenn der gesamte String numerisch sein muss.
  3. Number.isFinite statt !isNaN — fängt zusätzlich Infinity und 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

/ Weiter

Zurück zu Datentypen

Zur Übersicht