blog / javascript-fallstricke-einsteiger

7 JavaScript-Fallstricke die jeder Anfänger kennen sollte

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:

  1. Nutze === statt == (99% der Zeit)
  2. Nutze const/let statt var (immer)
  3. Verstehe Falsy Values (sei explizit bei Checks)
  4. Arrow Functions für Callbacks (behält this)
  5. Objekte/Arrays sind Referenzen (Spread für Copies)
  6. parseInt mit Radix (immer base angeben)
  7. 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


Über welche JavaScript-Falle bist du gestolpert? Teile deine Story!