Jeder fängt mal an
Meine ersten Tage mit JavaScript waren… chaotisch. Code, der funktionieren sollte, tat es nicht. Code, der nicht funktionieren sollte, tat es. JavaScript hat seine Eigenheiten, die besonders Anfänger überraschen.
Nach Jahren als JavaScript-Developer habe ich eine Liste der häufigsten Fallstricke zusammengestellt, über die ich selbst gestolpert bin.
1. == vs === (Loose vs Strict Equality)
Das Problem
// Überraschung!
console.log(0 == '0'); // true
console.log(0 == []); // true
console.log('0' == []); // false
// Was?!
console.log(null == undefined); // true
console.log(false == 0); // true
console.log('' == 0); // true
Warum passiert das?
== (loose equality) führt Type Coercion durch - JavaScript konvertiert beide Seiten zum gleichen Typ vor dem Vergleich.
Die Lösung
// Nutze IMMER === (strict equality)
console.log(0 === '0'); // false
console.log(null === undefined); // false
console.log(false === 0); // false
// Einzige Ausnahme:
if (value == null) {
// Prüft auf null UND undefined gleichzeitig
// Äquivalent zu: value === null || value === undefined
}
Faustregel: Nutze === zu 99%, außer du weißt GENAU was du tust.
2. var-Hoisting und Function Scope
Das Problem
function checkStock() {
console.log(available); // undefined (nicht ReferenceError!)
if (true) {
var available = 100;
}
console.log(available); // 100
}
Was passiert wirklich?
JavaScript “hebt” var-Deklarationen nach oben (hoisting):
function checkStock() {
var available; // Wird hierher gehoisted
console.log(available); // undefined
if (true) {
available = 100; // Nur die Zuweisung bleibt hier
}
console.log(available); // 100
}
Die Lösung
function checkStock() {
// console.log(available); // ReferenceError: Cannot access before initialization
if (true) {
let available = 100; // Block-scoped, kein Hoisting-Problem
console.log(available); // 100
}
// console.log(available); // ReferenceError: available is not defined
}
Faustregel: Nutze const als Standard, let wenn Reassignment nötig, niemals var.
Mehr dazu: Check mein Tutorial zu Variablen und Scopes für Details.
3. Automatisches Semikolon-Einfügen (ASI)
Das Problem
function getUser() {
return
{
name: 'Max',
age: 28
}
}
console.log(getUser()); // undefined (nicht das Objekt!)
Was passiert?
JavaScript fügt automatisch ein Semikolon nach return ein:
function getUser() {
return; // <- Semikolon automatisch eingefügt
{
name: 'Max',
age: 28
}
}
Die Lösung
function getUser() {
return {
name: 'Max',
age: 28
};
}
// Oder mit Klammer auf gleicher Zeile
function getUser() {
return {
name: 'Max',
age: 28
};
}
Faustregel: Return-Statement und Wert auf gleiche Zeile oder mit öffnender Klammer.
4. this-Binding in Functions vs Arrow Functions
Das Problem
const user = {
name: 'Max',
greet: function() {
console.log(`Hi, ich bin ${this.name}`);
},
greetDelayed: function() {
setTimeout(function() {
console.log(`Hi, ich bin ${this.name}`); // undefined
}, 1000);
}
};
user.greet(); // "Hi, ich bin Max"
user.greetDelayed(); // "Hi, ich bin undefined"
Warum?
Die Callback-Function in setTimeout hat ein eigenes this (zeigt auf window/global).
Die Lösung
const user = {
name: 'Max',
// Arrow Function behält das äußere 'this'
greetDelayed: function() {
setTimeout(() => {
console.log(`Hi, ich bin ${this.name}`); // Max
}, 1000);
},
// Oder mit explizitem bind
greetDelayedBound: function() {
setTimeout(function() {
console.log(`Hi, ich bin ${this.name}`); // Max
}.bind(this), 1000);
}
};
Faustregel: Arrow Functions für Callbacks, normale Functions für Methoden.
5. Falsy Values und Truthy Values
Das Problem
function checkAge(age) {
if (!age) {
console.log('Bitte Alter angeben');
return;
}
console.log(`Alter: ${age}`);
}
checkAge(0); // "Bitte Alter angeben" (Aber 0 ist ein valides Alter!)
checkAge(''); // "Bitte Alter angeben"
JavaScript’s Falsy Values
// Diese Werte sind "falsy":
false
0
-0
0n (BigInt zero)
'' (leerer String)
null
undefined
NaN
// ALLES andere ist "truthy"!
'0' // truthy
'false' // truthy
[] // truthy
{} // truthy
Die Lösung
function checkAge(age) {
// Explizit prüfen was du wirklich meinst
if (age === undefined || age === null) {
console.log('Bitte Alter angeben');
return;
}
console.log(`Alter: ${age}`);
}
// Oder kürzer (nullish check)
if (age == null) { // Prüft null UND undefined
console.log('Bitte Alter angeben');
}
Faustregel: Sei explizit bei deinen Checks. Nutze nicht !value wenn du null/undefined meinst.
Mehr dazu: Check mein Tutorial zu Datentypen und Type Casting.
6. Arrays und Objekte sind Referenzen
Das Problem
const original = { name: 'Max', age: 28 };
const copy = original;
copy.age = 30;
console.log(original.age); // 30 (nicht 28!)
console.log(original === copy); // true (gleiche Referenz)
Was passiert?
// 'copy' zeigt auf das GLEICHE Objekt wie 'original'
const original = { name: 'Max' }; // Objekt in Memory an Adresse 0x001
const copy = original; // 'copy' zeigt auch auf 0x001
Die Lösung
// Shallow Copy mit Spread Operator
const original = { name: 'Max', age: 28 };
const copy = { ...original };
copy.age = 30;
console.log(original.age); // 28
console.log(copy.age); // 30
// Für Arrays
const originalArray = [1, 2, 3];
const copyArray = [...originalArray];
// Für Deep Copies (verschachtelte Objekte)
const deepCopy = JSON.parse(JSON.stringify(original));
// Oder mit structuredClone (modern)
const deepCopy2 = structuredClone(original);
Achtung: Spread Operator macht nur Shallow Copy!
const original = {
name: 'Max',
address: { city: 'Berlin' }
};
const copy = { ...original };
copy.address.city = 'München';
console.log(original.address.city); // "München" (nested object wird referenziert!)
7. parseFloat und parseInt Überraschungen
Das Problem
console.log(parseInt('08')); // 8
console.log(parseInt('10 Äpfel')); // 10
console.log(parseInt('Äpfel 10')); // NaN
console.log(parseFloat('12.34.56')); // 12.34 (stoppt bei zweitem Punkt)
Noch überraschender
// Ohne Radix Parameter!
parseInt('10'); // 10
parseInt('010'); // 10 (in modernen Browsern)
parseInt('0x10'); // 16 (Hexadezimal!)
// Array.map Überraschung
['1', '2', '3'].map(parseInt);
// Erwartet: [1, 2, 3]
// Ergebnis: [1, NaN, NaN]
// Warum? map übergibt (value, index)
// parseInt('1', 0) -> 1 (Radix 0 = default 10)
// parseInt('2', 1) -> NaN (Radix 1 ist invalid)
// parseInt('3', 2) -> NaN (3 existiert nicht in Binary)
Die Lösung
// Nutze IMMER Radix bei parseInt
parseInt('10', 10); // 10 (Dezimal)
parseInt('010', 10); // 10 (nicht 8)
parseInt('10', 2); // 2 (Binär)
parseInt('10', 16); // 16 (Hexadezimal)
// Für Arrays: Wrapper-Function
['1', '2', '3'].map(str => parseInt(str, 10)); // [1, 2, 3]
// Oder Number()
['1', '2', '3'].map(Number); // [1, 2, 3]
Bonus: NaN Checks
const value = NaN;
// Das funktioniert NICHT
console.log(value === NaN); // false (NaN === NaN ist IMMER false!)
// Richtig:
console.log(isNaN(value)); // true (aber vorsichtig!)
console.log(Number.isNaN(value)); // true (besser!)
// isNaN vs Number.isNaN
isNaN('hello'); // true (coerced zu NaN)
Number.isNaN('hello'); // false (kein Typ-Conversion)
Faustregel: Nutze Number.isNaN() statt isNaN().
Meine Debugging-Strategie für diese Fehler
// 1. Logge Typen bei Vergleichen
console.log(typeof value1, typeof value2);
// 2. Nutze === für alle Vergleiche
// 3. Aktiviere ESLint mit recommended rules
// 4. Nutze TypeScript für Type Safety
// 5. Im Zweifelsfall: Explizit sein
if (value !== null && value !== undefined && value !== '') {
// Tue etwas
}
Fazit
JavaScript ist eine fantastische Sprache, aber sie hat ihre Tücken:
- Nutze
===statt==(99% der Zeit) - Nutze
const/letstattvar(immer) - Verstehe Falsy Values (sei explizit bei Checks)
- Arrow Functions für Callbacks (behält
this) - Objekte/Arrays sind Referenzen (Spread für Copies)
- parseInt mit Radix (immer base angeben)
- Number.isNaN() statt isNaN() (kein Type Coercion)
Diese Fallstricke zu kennen macht dich zu einem besseren JavaScript-Developer und spart dir Stunden an Debugging-Zeit.
Weiterführende Ressourcen
- JavaScript Grundlagen Tutorial
- Variablen und Scopes Tutorial
- Datentypen und Type Casting Tutorial
- You Don’t Know JS - Types & Grammar
- JavaScript.info - Type Conversions
Über welche JavaScript-Falle bist du gestolpert? Teile deine Story!