JavaScript teilt seine Wert-Welt in zwei Kategorien: sieben PrimitivesNumber, BigInt, String, Boolean, undefined, null und Symbol — und eine generische Object-Kategorie, die alles abdeckt, was kein Primitive ist (Arrays, Functions, Date, RegExp, Map, Set, Plain Objects). Diese Trennung entscheidet über fundamentale Eigenschaften jedes Wertes: Werden Veränderungen sichtbar, wenn der Wert herumgereicht wird? Sind zwei Werte gleich, wenn sie dasselbe „aussehen"? Was passiert, wenn man eine Methode aufruft? Dieser Artikel zeigt im Detail, wie die beiden Welten funktionieren — und warum die Unterscheidung in praktisch jeder Zeile JavaScript-Code mitspielt.

Zwei Welten: Primitives und Objects

Die ECMAScript-Spezifikation definiert acht „Language Types": die sieben Primitives plus die Object-Kategorie. Primitives sind atomare, immutable Werte — eine Zahl, ein String, ein Boolean. Objects dagegen sind Container für beliebig viele Slots (Properties), die im Lauf der Zeit geändert werden können. Der Unterschied wirkt sich an drei zentralen Stellen aus: in der Identität (was bedeutet „gleich"?), in der Übergabe (was passiert beim Funktionsaufruf?) und in der Lebensdauer (kann sich der Wert ändern, ohne dass ich es merke?).

JavaScript zwei-welten.js
// Primitive — by value, immutable
let a = 5;
let b = a;       // Wert kopiert
b = 99;
console.log(a);  // 5  — unverändert

// Object — by reference, mutable
const obj1 = { wert: 5 };
const obj2 = obj1;       // Reference kopiert
obj2.wert = 99;
console.log(obj1.wert);  // 99 — selber Container
Output
5
99

Die sieben Primitives im Detail

Jedes Primitive hat einen klar definierten Wertebereich, ein eindeutiges typeof-Ergebnis und — bis auf null und undefined — einen Wrapper-Konstruktor, über den das Auto-Boxing läuft. BigInt kam erst mit ES2020 dazu; davor sprach man von „sechs Primitives".

PrimitivetypeofWertebereichBeispiel
Number'number'IEEE 754 64-Bit, ±2^53 sichere Integer42, 3.14, NaN
BigInt'bigint'beliebig große Ganzzahlen9007199254740993n
String'string'UTF-16-Code-Unit-Sequenzen'hallo', "text"
Boolean'boolean'true oder falsetrue
undefined'undefined'nur ein einziger Wert: undefinedundefined
null'object' (!)nur ein einziger Wert: nullnull
Symbol'symbol'jeder Symbol-Aufruf erzeugt einen neuen WertSymbol('id')

null und undefined sind Sonderlinge: sie sind die einzigen Werte, die kein Wrapper-Object haben — und die einzigen, die einen TypeError werfen, wenn man eine Property auf ihnen anspricht.

JavaScript alle-7-primitives.js
console.log(typeof 42);            // 'number'
console.log(typeof 9007199254740993n); // 'bigint'
console.log(typeof 'hallo');       // 'string'
console.log(typeof true);          // 'boolean'
console.log(typeof undefined);     // 'undefined'
console.log(typeof null);          // 'object'  (historischer Bug)
console.log(typeof Symbol('id'));  // 'symbol'
Output
number
bigint
string
boolean
undefined
object
symbol

Was bedeutet „primitive"?

Drei Eigenschaften definieren ein Primitive: Immutability, By-Value-Vergleich und By-Value-Übergabe. Ein primitiver Wert kann nicht mutiert werden — alle String-Methoden, alle Number-Operationen erzeugen neue Werte. Zwei Primitives sind gleich, wenn ihr Wert gleich ist; es gibt keinen Begriff von „selbe Identität". Und wenn ein Primitive einer Funktion übergeben wird, bekommt die Funktion eine eigene Kopie.

JavaScript primitive-immutability.js
const s = 'hallo';
s[0] = 'H';            // wirkungslos — Strings sind immutable
console.log(s);        // 'hallo'

const upper = s.toUpperCase(); // gibt NEUEN String zurück
console.log(s);        // 'hallo'  — Original unverändert
console.log(upper);    // 'HALLO'

// Number genauso:
let n = 5;
n.eigeneProp = 99;     // wirkungslos (silently im strict mode → TypeError)
console.log(n.eigeneProp); // undefined
Output
hallo
hallo
HALLO
undefined

Engines optimieren Primitives intensiv: kleine Integer werden oft direkt im Tagged-Pointer kodiert (Smi — Small Integer in V8), Strings werden geteilt und intern dedupliziert. Diese Optimierungen sind genau deshalb möglich, weil die Sprache garantiert, dass sich der Wert nicht ändert.

Objects und ihre Subtypen

Alles, was kein Primitive ist, ist ein Object — auch wenn es nicht so aussieht. Ein Array ist ein Object mit numerischen Indices als Property-Namen. Eine Function ist ein Object, das zusätzlich aufrufbar ist. Eine Date ist ein Object mit internem Timestamp-Slot. Diese Vielfalt sieht man an typeof: alle liefern 'object' — bis auf Functions, die eigens 'function' zurückgeben.

JavaScript object-subtypen.js
console.log(typeof {});                  // 'object'
console.log(typeof []);                  // 'object'
console.log(typeof new Date());          // 'object'
console.log(typeof /regex/);             // 'object'
console.log(typeof new Map());           // 'object'
console.log(typeof new Set());           // 'object'
console.log(typeof new Error('x'));      // 'object'

console.log(typeof function () {});      // 'function'  (Sonderfall)
console.log(typeof class C {});          // 'function'  (Class ist syntaktisch eine Function)
Output
object
object
object
object
object
object
object
function
function

Wer feiner unterscheiden möchte, nutzt Array.isArray(), instanceof Date, oder den verlässlichen Trick Object.prototype.toString.call(wert), der '[object Array]', '[object Date]', '[object RegExp]' etc. zurückgibt.

Die typeof-Tabelle vollständig

typeof ist der schnellste Weg, den Typ eines Wertes zur Laufzeit zu erfragen. Das Ergebnis ist immer ein String. Die Tabelle deckt alle Möglichkeiten ab — inklusive des berühmten null-Bugs.

Wert / Typtypeof-ErgebnisAnmerkung
undefined'undefined'auch bei undeklarierten Variablen
null'object'historischer Bug aus der ersten JS-Engine
true / false'boolean'
42, 3.14, NaN, Infinity'number'auch NaN und ±Infinity sind Numbers
123n'bigint'seit ES2020
'text''string'
Symbol('id')'symbol'
{} / [] / new Date()'object'alle nicht-Function-Objects
function(){} / class {}'function'Sonderfall, eigentlich auch Object

Der typeof null === 'object'-Effekt geht auf die allererste JavaScript-Implementation von 1995 zurück: Werte wurden als 32-Bit-Tagged-Union kodiert, mit drei Type-Tag-Bits. Object hatte den Tag 0, und null war der NULL-Pointer mit allen Bits auf 0 — fiel also automatisch in den Object-Bucket. Ein Vorschlag, das in ES5.1 zu reparieren, wurde verworfen, weil zu viel Code inzwischen implizit darauf baute.

Auto-Boxing — Methoden auf Primitives

Primitives haben keine eingebauten Methoden — 'hallo'.toUpperCase() müsste eigentlich einen Fehler werfen. Tut es aber nicht, weil JavaScript bei jedem Property-Zugriff auf einem Primitive einen temporären Wrapper-Object erzeugt, die Methode darauf aufruft und das Wrapper-Object danach verwirft. Dieser Mechanismus heißt Auto-Boxing.

JavaScript auto-boxing.js
const s = 'hallo';
// Hinter den Kulissen:
//   tmp = new String(s);    // Wrapper-Object
//   result = tmp.toUpperCase();
//   (tmp wird verworfen)
console.log(s.toUpperCase()); // 'HALLO'
console.log(s.length);        // 5

const n = 3.14159;
console.log(n.toFixed(2));    // '3.14'  — Number-Wrapper

const b = true;
console.log(b.toString());    // 'true'  — Boolean-Wrapper
Output
HALLO
5
3.14
true

Wichtig: das Box-Object wird nicht persistiert. Wer einer Property auf einem Primitive einen Wert zuweist, schreibt in den temporären Wrapper, der direkt danach verschwindet — die Zuweisung ist effektiv wirkungslos.

Daraus folgt eine wichtige Anti-Pattern-Regel: niemals new String('x'), new Number(5) oder new Boolean(true) benutzen. Diese Konstruktoren erzeugen persistent gemachte Wrapper-Objects mit typeof 'object', die sich in fast jeder Hinsicht anders verhalten als das primitive Pendant.

JavaScript wrapper-anti-pattern.js
const s1 = 'hallo';
const s2 = new String('hallo');

console.log(typeof s1);   // 'string'
console.log(typeof s2);   // 'object'  — anders!
console.log(s1 === s2);   // false     — String !== Object
console.log(s1 == s2);    // true      — Coercion macht == lückenhaft

// Boolean-Wrapper ist besonders heimtückisch:
const b = new Boolean(false);
if (b) {
    console.log('wird ausgeführt!'); // weil Object truthy ist
}
Output
string
object
false
true
wird ausgeführt!

Reference vs. Value bei Funktionsaufrufen

Wenn eine Funktion ein Argument bekommt, hängt das Verhalten am Typ. Primitives werden kopiert — die Funktion arbeitet auf einer eigenen, lokalen Kopie. Objects werden als Reference übergeben — die Funktion sieht denselben Container wie der Aufrufer.

JavaScript reference-vs-value.js
function aendern(p, o) {
    p = 999;        // wirkt nur lokal — Kopie wird verändert
    o.wert = 999;   // wirkt nach außen — Container ist geteilt
    o = { wert: 0 }; // lokale Re-Bindung — wirkt nicht nach außen
}

const zahl = 42;
const objekt = { wert: 42 };

aendern(zahl, objekt);

console.log(zahl);          // 42       — Primitive, unberührt
console.log(objekt.wert);   // 999      — Object, mutiert
Output
42
999

Die Verwirrung ist häufig: JavaScript ist nicht „pass by reference" im klassischen Sinn (wie etwa C++-Referenzen oder Pascal-var-Parameter). Es ist immer „pass by value" — aber bei Objects ist der Wert eben eine Reference. Eine Re-Bindung des Parameters innerhalb der Funktion (o = ...) ändert nichts am Original.

Mutability — Primitives sind eingefroren, Objects nicht

Diese Regel ist ohne Ausnahme: jedes Primitive ist immutable. Strings, Numbers, Symbols — kein Aufruf, keine Methode kann den Wert verändern. Was sich ändert, ist die Variable, die auf einen anderen Wert zeigt. Bei Objects ist das genau umgekehrt: Properties können hinzugefügt, geändert, gelöscht werden, ohne dass die Variable selbst neu gebunden werden muss.

JavaScript mutability.js
// Primitive: man bekommt einen NEUEN Wert
let s = 'hallo';
s = s + ' welt';        // s zeigt jetzt auf einen NEUEN String
console.log(s);         // 'hallo welt'

// Object: man verändert DENSELBEN Container
const arr = [1, 2, 3];
arr.push(4);            // arr zeigt weiter auf dasselbe Array
arr[0] = 99;
console.log(arr);       // [99, 2, 3, 4]

// Wer ein Object einfrieren möchte:
const fix = Object.freeze({ a: 1 });
fix.a = 99;             // im strict mode: TypeError; sonst still ignoriert
console.log(fix.a);     // 1
Output
hallo welt
[ 99, 2, 3, 4 ]
1

Object.freeze ist allerdings flach — innere Objects bleiben mutierbar. Für tiefe Immutability braucht man entweder rekursives Deep-Freeze oder eine Library wie Immer.

Identity bei === — Primitives vs. Objects

=== (Strict Equality) verhält sich an einer entscheidenden Stelle anders, je nachdem ob die Operanden Primitives oder Objects sind. Bei Primitives ist === ein Wert-Vergleich: zwei Strings mit demselben Inhalt sind gleich, zwei Numbers mit demselben Wert sind gleich. Bei Objects ist === ein Identitäts-Vergleich: zwei Objects sind nur dann gleich, wenn sie dieselbe Reference sind — also derselbe Container.

JavaScript identity-vergleich.js
// Primitives — gleich, wenn der Wert gleich ist
console.log('hallo' === 'hallo');  // true
console.log(42 === 42);            // true
console.log(true === true);        // true

// Objects — gleich nur bei selber Reference
const a = { x: 1 };
const b = { x: 1 };
const c = a;

console.log(a === b);   // false  — verschiedene Container
console.log(a === c);   // true   — selbe Reference
Output
true
true
true
false
true

Eine spezielle Variante ist Object.is. Sie verhält sich wie === mit zwei Ausnahmen: Object.is(NaN, NaN) ist true (während NaN === NaN immer false ist), und Object.is(+0, -0) ist false (während +0 === -0 true ist).

JavaScript object-is.js
console.log(NaN === NaN);              // false
console.log(Object.is(NaN, NaN));      // true

console.log(+0 === -0);                // true
console.log(Object.is(+0, -0));        // false

// Beim Object-Vergleich identisch zu ===
const o = {};
console.log(Object.is(o, o));          // true
console.log(Object.is({}, {}));        // false
Output
false
true
true
false
true
false

Primitives vs. Objects — die Übersicht

Die zentrale Tabelle zum Mitnehmen: jede Zeile ist ein konkreter Verhaltens-Unterschied zwischen den beiden Welten.

AspektPrimitivesObjects
Speicherort (Engine)oft Stack / Tagged-PointerHeap
Mutabilityimmutablemutable (außer Object.freeze)
===-Vergleichby valueby reference
Funktions-Übergabeby value (Kopie)by reference (geteilter Container)
Methoden-Aufrufvia Auto-Boxing (temp. Wrapper)direkt auf Property
Property-Zuweisungwirkungslos / TypeErrorpersistent
typeofspezifisch ('string', 'number'...)'object' oder 'function'
JSONdirekt serialisierbarrekursiv serialisierbar
Property-Schlüsselnicht möglich (außer String/Symbol)als Schlüssel nutzbar

Symbol als Sonderfall

Symbol ist seit ES2015 das siebte Primitive (technisch das sechste — BigInt kam später). Jeder Symbol()-Aufruf erzeugt einen neuen, eindeutigen Wert, der mit keinem anderen Symbol gleich ist. Das macht Symbols ideal als kollisions-freie Property-Schlüssel — etwa für Library-Code, der nicht mit User-Properties in Konflikt geraten soll.

JavaScript symbol-grundlagen.js
const s1 = Symbol('id');
const s2 = Symbol('id');

console.log(s1 === s2);    // false  — jeder Aufruf eigen
console.log(typeof s1);    // 'symbol'

// Als Property-Schlüssel
const SECRET = Symbol('secret');
const user = {
    name: 'Anna',
    [SECRET]: 'verborgen'
};
console.log(user[SECRET]); // 'verborgen'
console.log(Object.keys(user)); // [ 'name' ]  — Symbol nicht enumerable
Output
false
symbol
verborgen
[ 'name' ]

Eine Vertiefung mit Well-Known-Symbols, Symbol.iterator und Symbol.for findet sich im dedizierten Symbol-Tutorial: /docs/javascript/symbols-reflect-proxy/symbol/.

Bemerkenswertes & Hintergrund

typeof null gibt 'object' — historischer Bug

In der allerersten JavaScript-Implementation von 1995 wurden Werte als 32-Bit-Tagged-Union mit drei Type-Tag-Bits kodiert. Object hatte den Tag 0, und null war als NULL-Pointer mit allen Bits auf 0 repräsentiert — fiel also automatisch in den Object-Bucket. Ein Fix-Vorschlag wurde später verworfen, weil zu viel Bestandscode implizit darauf baut.

typeof function gibt 'function' — Convenience-Sonderfall

Functions sind eigentlich Objects (haben Properties, einen Prototype, lassen sich erweitern), aber typeof macht für sie eine Ausnahme. Der Grund ist rein praktisch: in den 1990ern war Feature-Detection à la typeof obj.method === 'function' ein häufiges Idiom, und es schien sinnvoll, die Aufrufbarkeit ohne Umweg über instanceof Function abfragen zu können.

BigInt seit ES2020 als 7. Primitive

Vor ES2020 sprach man von sechs Primitives. BigInt wurde nötig, weil Number nur ganze Zahlen bis ±2^53 − 1 exakt repräsentieren kann — alles darüber verliert Präzision. BigInt hat keine Obergrenze, kann aber nicht direkt mit Number in Arithmetik gemischt werden — 1n + 1 wirft TypeError.

new Number(5) erzeugt einen Object-Wrapper

Das ist eine echte Falle: new Number(5) hat typeof 'object', ist nicht === 5, und new Boolean(false) ist sogar truthy in if-Bedingungen. Niemals mit new aufrufen — die Konstruktoren sind nur für Coercion gedacht: Number('5'), String(true), Boolean(0) — alle ohne new liefern Primitives.

Strings sind UTF-16-Code-Unit-Sequenzen, nicht Codepoints

'a'.length === 1, aber '😀'.length === 2 — weil das Emoji als Surrogate-Pair (zwei UTF-16-Code-Units) kodiert ist. Auch '😀'[0] liefert eine ungültige halb-Surrogate. Wer Codepoints zählen möchte, nutzt [...str].length oder Array.from(str).length; beide iterieren über volle Codepoints.

Object.is unterscheidet sich von === bei NaN und ±0

NaN === NaN ist false (per IEEE-754-Spec), Object.is(NaN, NaN) ist true. +0 === -0 ist true, Object.is(+0, -0) ist false. Für Generic-Equality (z. B. in Set/Map-Keys) nutzt JavaScript intern den „SameValueZero"-Algorithmus — eine Mischform: NaN ist gleich NaN, aber +0 und -0 ebenfalls.

Symbol gehört zu den Primitives

Auch wenn Symbols sich oft wie Objects anfühlen (Methoden-Calls, Description-Property), sind sie laut Spec ein Primitive. Sie haben kein Wrapper-Object, das man mit new erzeugen könnte (new Symbol() wirft TypeError) — die einzige Variante ist die Konstruktor-Funktion ohne new: Symbol('id').

structuredClone (ES2022) ersetzt JSON-Hack für Deep-Clone

Vor 2022 war JSON.parse(JSON.stringify(obj)) der Standard-Trick für Deep-Clones — mit den bekannten Schwächen: keine Functions, keine Dates (werden zu Strings), keine zirkulären Referenzen. structuredClone(obj) löst alle drei Probleme: Dates bleiben Dates, Maps und Sets werden korrekt geklont, zyklische Strukturen werden erkannt. Functions und DOM-Nodes werden allerdings weiter abgelehnt.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Datentypen

Zur Übersicht