Die meisten Programmiersprachen kennen genau einen Wert für „nichts": null in Java, nil in Ruby, None in Python. JavaScript hat zwei: null und undefined. Die Trennung wirkt zunächst wie ein Designfehler, hat aber eine klare Logik — undefined bedeutet „noch nicht gesetzt" (eine Sache, die die Engine selbst sagt), null bedeutet „explizit ohne Wert" (eine Sache, die der Programmierer sagt). Dieser Artikel zeigt im Detail, wann welcher Wert auftritt, wie sie sich in ==, ===, ??, JSON, Default-Parametern und Optional Chaining unterscheiden — und welches Idiom (== null) beide gleichzeitig abfängt.
Zwei „leere" Werte
JavaScript trennt zwei Konzepte sauber: die Abwesenheit eines Wertes (Sprache hat noch nichts gesetzt) und die explizite Leere (Programmierer hat bewusst „nichts" gewählt). Die ECMAScript-Spec dokumentiert beides als eigene Primitive-Typen mit jeweils einem einzigen Wert.
let unset; // automatisch undefined — Sprache sagt: noch nicht gesetzt
let leer = null; // explizit null — Programmierer sagt: kein Wert
console.log(unset); // undefined
console.log(leer); // null
console.log(typeof unset); // 'undefined'
console.log(typeof leer); // 'object' (historischer Bug)undefined
null
undefined
objectundefined: „nicht gesetzt"
undefined ist der Wert, den JavaScript an mehreren Stellen automatisch einsetzt — überall dort, wo etwas vom Programmierer hätte gesetzt werden können, aber nicht wurde. Vier kanonische Quellen.
// 1) Nicht-initialisierte Variable
let x;
console.log(x); // undefined
// 2) Fehlender Function-Parameter
function gruss(name) {
return `Hallo ${name}`;
}
console.log(gruss()); // 'Hallo undefined'
// 3) Fehlender Property-Zugriff
const obj = { a: 1 };
console.log(obj.b); // undefined
// 4) Function ohne return
function nichts() { /* still */ }
console.log(nichts()); // undefined
// Bonus: Array-Loch
const arr = [1, , 3]; // sparse array
console.log(arr[1]); // undefinedundefined
Hallo undefined
undefined
undefined
undefinedIn all diesen Fällen hat niemand explizit undefined gesetzt — die Sprache hat es als Default eingefügt, weil ein Wert fehlte.
null: „explizit leer"
null taucht nie automatisch auf. Wer null sieht, weiß: hier hat jemand bewusst entschieden, dass dieser Slot leer sein soll. Das macht null zum natürlichen Sentinel-Wert für API-Antworten („User nicht gefunden"), für aufgegebene Reference-Variablen (Memory-Hint an den GC) oder für das Ende einer Prototype-Chain.
// API-Antwort signalisiert "nicht gefunden" via null
function findeUser(id) {
const user = datenbank.get(id);
return user ?? null; // explizit: kein Treffer
}
// DOM: querySelector liefert null bei keinem Match
const el = document.querySelector('.gibts-nicht');
if (el === null) {
console.log('Kein Element gefunden');
}
// Prototype-Chain endet bei null
const obj = {};
console.log(Object.getPrototypeOf(Object.prototype)); // nullKein Element gefunden
nullDiese Konvention ist in der DOM-Welt fest verankert: querySelector, getElementById, parentNode und nextElementSibling geben alle null zurück, wenn nichts da ist — niemals undefined.
Konvention: wann was?
Die übliche Daumenregel in modernen Codebasen:
undefined= „nicht gesetzt", „nicht relevant", „nicht angegeben". Tritt bei optionalen Parametern, fehlenden Properties, frisch deklarierten Variablen auf.null= „bewusst ohne Wert", „expliziter Reset", „leerer Sentinel". Tritt in API-Verträgen, Datenmodellen, expliziten Lösch-Operationen auf.
Manche Codebasen normalisieren auf nur eine der beiden — typischerweise null für Datenbank-/JSON-Lasten und undefined für interne Sprach-Slots. Beide Strategien sind valide; wichtig ist, dass das Team sich auf eine einigt.
// Konvention: API-Datenmodell nutzt null für "leer"
type User = {
name: string;
telefon: string | null; // explizit null wenn nicht hinterlegt
partner: User | null;
};
// Konvention: Funktions-Optionen nutzen undefined
function rendere(opt) {
const farbe = opt?.farbe ?? '#000'; // undefined → Default
const groesse = opt?.groesse ?? 16;
// ...
}typeof-Verhalten und der null-Bug
typeof undefined liefert sauber 'undefined'. typeof null liefert dagegen 'object' — der berühmte historische Bug aus der ersten JS-Implementation, der aus Backwards-Compat-Gründen nie repariert wurde. Daraus folgt: für robuste Tests auf null oder undefined nutzt man immer den direkten Vergleich, nicht typeof.
console.log(typeof undefined); // 'undefined'
console.log(typeof null); // 'object' (Bug)
// Robuste Tests:
const wert = null;
if (wert === undefined) { /* nur undefined */ }
if (wert === null) { /* nur null */ }
if (wert == null) { /* beide — siehe nächste Section */ }
// Nicht-robust (wegen typeof null === 'object'):
if (typeof wert === 'object') {
// greift auch für null! Falsch wenn man Plain-Object erwartet.
}Eine klassische Falle: if (typeof wert === 'object') { wert.eigenschaft } — bei wert === null wirft das einen TypeError, weil null eben kein „echtes" Object ist, obwohl typeof es so behauptet. Korrekt wäre if (wert !== null && typeof wert === 'object').
Vergleichs-Verhalten — == vs. ===
Bei === (Strict Equality) sind null und undefined zwei verschiedene Werte: null === undefined ist false. Bei == (Loose Equality) macht JavaScript eine Sonder-Regel: null == undefined ist true. Diese Sonder-Regel ist die einzige Stelle, an der die meisten modernen Linter eine Ausnahme von „immer === benutzen" zulassen — denn das Idiom value == null ist ein extrem kompakter Test auf „beide leeren Werte gleichzeitig".
// Idiom: == null fängt beide ab
function isLeer(v) {
return v == null; // true für null UND undefined
}
console.log(isLeer(null)); // true
console.log(isLeer(undefined)); // true
console.log(isLeer(0)); // false
console.log(isLeer('')); // false
console.log(isLeer(false)); // false
console.log(isLeer(NaN)); // false
// Strikt-Variante (expliziter, doppelt so lang)
function isLeerStrict(v) {
return v === null || v === undefined;
}true
true
false
false
false
falsevoid 0 — die historische Konstante
void ist ein unärer Operator, der seinen Operanden auswertet und immer undefined zurückgibt. void 0 ist die kürzeste Variante und tauchte historisch in zwei Kontexten auf: erstens als sichere Konstante, weil in alten JavaScript-Engines undefined selbst als Identifier überschreibbar war (undefined = 'oops' war legal), zweitens in Minified-Code, wo void 0 drei Zeichen kürzer ist als undefined.
console.log(void 0); // undefined
console.log(void 'irgendwas'); // undefined
console.log(void (1 + 2)); // undefined
// Historisch: sichere Variante
if (x === void 0) {
// gleiche Bedeutung wie x === undefined
}undefined
undefined
undefinedIn modernem Code (ES5+) ist undefined ein non-writable, non-configurable Property auf globalThis — überschreiben geht nicht mehr. Damit ist void 0 heute überflüssig im Source-Code; in Bundler-Output kommt es weiter vor.
JSON: null ja, undefined nein
JSON kennt nur null, kein undefined — das ist eine harte Spec-Regel. JSON.stringify reagiert entsprechend unterschiedlich: Properties mit undefined-Wert werden komplett weggelassen, Properties mit null-Wert werden als "null" serialisiert. In Arrays werden undefined-Slots zu null umgesetzt, weil ein Array keine Lücken haben darf.
const obj = {
a: 1,
b: undefined, // verschwindet
c: null, // bleibt als "null"
d: 'text'
};
console.log(JSON.stringify(obj));
// '{"a":1,"c":null,"d":"text"}'
// Arrays: undefined wird zu null
const arr = [1, undefined, null, 4];
console.log(JSON.stringify(arr));
// '[1,null,null,4]'
// Round-Trip: undefined kann nicht wiederhergestellt werden
const klon = JSON.parse(JSON.stringify(obj));
console.log(klon);
// { a: 1, c: null, d: 'text' } — b ist weg{"a":1,"c":null,"d":"text"}
[1,null,null,4]
{ a: 1, c: null, d: 'text' }Das ist auch der Grund, warum API-Schemas (REST, GraphQL) in der Regel null als „explizit leer" definieren: nur null überlebt die Serialisierung. Wer undefined über die Wire schicken will, muss es vorher in null umwandeln.
?? — Nullish Coalescing
Der ??-Operator (ES2020) ist explizit auf null und undefined zugeschnitten: er liefert den linken Wert, außer wenn dieser nullish ist — dann den rechten. Das unterscheidet ihn vom älteren ||, das auf jeden Falsy-Wert reagiert (also auch auf 0, '', false, NaN).
console.log(null ?? 'default'); // 'default'
console.log(undefined ?? 'default'); // 'default'
console.log(0 ?? 'default'); // 0 — nicht überschrieben!
console.log('' ?? 'default'); // ''
console.log(false ?? 'default'); // false
console.log(NaN ?? 'default'); // NaN
// Vergleich mit || (die alte Falle)
const lautstaerke = 0;
console.log(lautstaerke || 50); // 50 — Bug! 0 wird durch Default ersetzt
console.log(lautstaerke ?? 50); // 0 — korrekt: 0 ist ein gültiger Wertdefault
default
0
false
NaN
50
0?? ist die richtige Wahl für numerische Defaults und für boolean Defaults, wo 0 und false gültige Werte sein dürfen. || bleibt brauchbar für reine String-Defaults, wo der leere String und null/undefined denselben Effekt haben sollen.
Praxis: Defaults und Optional Chaining
Drei moderne Sprach-Features arbeiten zusammen, um nullish-Werte sauber zu handhaben: Default-Parameter in Funktions-Signaturen, Optional Chaining (?.) für sichere Property-Zugriffe und Nullish Coalescing (??) für Fallbacks. Wichtig zu wissen: Default-Parameter greifen nur bei undefined, nicht bei null. null wird als „expliziter Wert" durchgelassen.
function konfiguriere(opt = {}) {
// 1) Default-Parameter: greift nur bei undefined
const farbe = opt.farbe ?? '#000';
const groesse = opt.groesse ?? 16;
// 2) Optional Chaining für verschachtelten Zugriff
const tema = opt?.theme?.name ?? 'default';
// 3) Optional Chaining mit Methodenaufruf
opt?.onInit?.();
return { farbe, groesse, tema };
}
console.log(konfiguriere());
// { farbe: '#000', groesse: 16, tema: 'default' }
console.log(konfiguriere({ farbe: '#f00', theme: { name: 'dark' } }));
// { farbe: '#f00', groesse: 16, tema: 'dark' }
// Default-Parameter ignoriert null nicht:
function f(x = 'default') { return x; }
console.log(f()); // 'default'
console.log(f(undefined)); // 'default' — Default greift
console.log(f(null)); // null — Default greift NICHT{ farbe: '#000', groesse: 16, tema: 'default' }
{ farbe: '#f00', groesse: 16, tema: 'dark' }
default
default
nullnull vs. undefined — die Übersicht
| Aspekt | undefined | null |
|---|---|---|
| Quelle | Engine setzt automatisch | Programmierer setzt explizit |
| Bedeutung | „noch nicht gesetzt" | „explizit ohne Wert" |
typeof | 'undefined' | 'object' (historischer Bug) |
== Vergleich | null == undefined ist true | null == undefined ist true |
=== Vergleich | nur gegen undefined true | nur gegen null true |
| JSON-Serialisierung | Property entfällt; Array → null | bleibt als "null" |
?? Operator | Default greift | Default greift |
|| Operator | Default greift (mit Falsy-Falle) | Default greift (mit Falsy-Falle) |
| Default-Parameter | greift | greift NICHT |
Optional Chaining (?.) | bricht ab, gibt undefined zurück | bricht ab, gibt undefined zurück |
| Numerische Coercion | +undefined → NaN | +null → 0 |
Eine Detail-Falle: null + 1 ist 1 (weil null zu 0 gecoerced wird), aber undefined + 1 ist NaN. Wer numerisch rechnet, sollte sich auf keine der beiden Coercions verlassen — explizit konvertieren oder ?? für Defaults nutzen.
Häufig gestellte Fragen
Wann nutzt man null statt undefined?
null dort, wo man EXPLIZIT „kein Wert" signalisieren will — typischerweise als API-Antwort („User nicht gefunden"), als Datenbank-NULL-Mapping, als bewussten Reset einer Reference-Variable. undefined dort, wo „noch nicht gesetzt" gemeint ist — fehlende optionale Parameter, nicht-initialisierte Variablen, fehlende Object-Properties. Manche Codebasen normalisieren bewusst auf nur einen der beiden Werte; das ist legitim, solange das Team sich an die Konvention hält.
Warum ist typeof null gleich 'object'?
Frühe JavaScript-Implementierungen (1995) kodierten Werte als Tagged-Pointer: drei Bits Type-Tag, der Rest war der Wert. Object hatte den Type-Tag 0, und der NULL-Pointer (0x00) hatte ebenfalls alle Bits auf null — also Tag 0, also Object. Ein Vorschlag, das in ES5.1 zu reparieren (typeof null === 'null'), wurde nach Diskussion verworfen, weil zu viele Codebasen implizit auf das alte Verhalten bauen.
Soll ich `== null` oder `=== null || === undefined` schreiben?
Beides ist akzeptabel. value == null ist idiomatisch, kürzer und in der ESLint-Regel eqeqeq mit Option "allow-null" ausdrücklich erlaubt. value === null || value === undefined ist expliziter und passt zu Code-Standards, die ALLE ==-Verwendungen verbieten. Wichtig: konsistent bleiben innerhalb eines Projekts — beide Varianten ständig zu mischen verwirrt mehr, als die Wahl entscheidet.
Wird undefined in JSON serialisiert?
Nein. JSON.stringify({ a: undefined }) ergibt '{}' — die Property wird komplett weggelassen. In Arrays werden undefined-Slots zu null: JSON.stringify([1, undefined, 3]) ergibt '[1,null,3]'. Round-Trip JSON.parse(JSON.stringify(obj)) verliert also alle undefined-Properties — wer das nicht möchte, muss vor dem Stringify auf null normalisieren.
Was passiert bei `null + 1` und `undefined + 1`?
null wird in numerischem Kontext zu 0 gecoerced, also null + 1 === 1. undefined wird zu NaN gecoerced, also undefined + 1 ergibt NaN. Das ist eine selten genutzte Asymmetrie, die regelmässig zu subtilen Bugs führt — etwa wenn ein optionaler Parameter weggelassen wird (NaN-Folge) oder explizit auf null gesetzt wird (0-Folge). Beste Praxis: vor numerischen Operationen explizit defaulten, z. B. (x ?? 0) + 1.
Default-Parameter mit null oder undefined?
Default-Parameter greifen NUR bei undefined, nicht bei null. function f(x = 'd') { return x }: f() liefert 'd', f(undefined) liefert 'd', f(null) liefert null. Das ist gewollt — null ist eine bewusste Wahl, die nicht überschrieben werden soll. Wer auch bei null defaulten will, nutzt ?? innerhalb der Funktion: const v = x ?? 'd';.
?? oder || — wann was?
?? reagiert nur auf null und undefined; || reagiert auf alle Falsy-Werte (0, '', false, NaN, null, undefined). Bei numerischen oder boolean Defaults ist ?? fast immer richtig: lautstaerke ?? 50 respektiert 0 als gültigen Wert, lautstaerke || 50 nicht. || bleibt sinnvoll für rein optionale Strings, wo der leere String wirklich „kein Wert" bedeutet.
Optional Chaining mit Methoden — wie geht das?
obj.method?.() ruft die Methode nur auf, wenn obj.method nicht null oder undefined ist; sonst ist der Gesamtausdruck undefined. Auch verschachtelt: obj?.sub?.method?.(arg) bricht beim ersten nullish-Wert ab. Wichtig: das ?. kommt VOR der aufrufenden Klammer (method?.(), nicht method.()?). Der Operator gilt ausschliesslich für nullish-Werte — wenn method z. B. 0 ist, wirft der Aufruf wie üblich einen TypeError.