6 Sprachkonzepte

6.1 Variablen, Datentypen und Typkonvertierung

JavaScript ist eine dynamisch typisierte Sprache, in der Variablen ohne explizite Typdeklaration verwendet werden können. Diese Flexibilität bietet einerseits Freiheiten bei der Entwicklung, erfordert andererseits aber auch ein tiefes Verständnis der zugrundeliegenden Mechanismen, um häufige Fehlerquellen zu vermeiden.

6.1.1 Variablendeklaration

In JavaScript können Variablen auf unterschiedliche Weise deklariert werden:

// var - funktionsweiter Scope (veraltet)
var counter = 42;

// let - blockweiter Scope (empfohlen)
let username = "developer";

// const - unveränderliche Referenz (empfohlen für Werte, die sich nicht ändern)
const PI = 3.14159;

Die Verwendung von let und const (eingeführt mit ES6) ist der älteren var-Deklaration vorzuziehen, da sie ein vorhersehbareres Verhalten durch ihren Block-Scope bieten. Hierdurch werden auch viele der klassischen Probleme mit Variablen-Hoisting vermieden, auf die wir im Kapitel “Funktionen, Scopes und Closures” näher eingehen werden.

6.1.2 Primitive Datentypen

JavaScript kennt sieben primitive Datentypen:

// Number - Fließkommazahlen und Ganzzahlen
let count = 42;
let price = 9.99;
let infinity = Infinity;
let notANumber = NaN;

// String - Textdaten
let greeting = "Hallo Welt";
let multiline = `Dies ist ein
mehrzeiliger Text`; // Template-Strings (ES6)

// Boolean - Wahrheitswerte
let isActive = true;

// Undefined - Deklarierte, aber nicht initialisierte Variable
let undefinedVar;

// Null - Absichtliche Abwesenheit eines Wertes
let emptyValue = null;

// Symbol - Eindeutige, unveränderliche Werte (ES6)
let uniqueKey = Symbol('id');

// BigInt - Ganzzahlen mit beliebiger Präzision (ES11)
let bigNumber = 9007199254740991n;

6.1.3 Dynamische Typisierung

Eine Besonderheit von JavaScript ist die Möglichkeit, den Datentyp einer Variable während der Laufzeit zu ändern:

let value = 42;      // Number
value = "42";        // jetzt ein String
value = true;        // jetzt ein Boolean

Diese Flexibilität kann vorteilhaft sein, birgt aber auch Risiken hinsichtlich der Codequalität und Wartbarkeit. In größeren Projekten empfiehlt sich daher oft der Einsatz von TypeScript (siehe Kapitel “TypeScript als JavaScript-Superset”), um statische Typisierung einzuführen.

6.1.4 Referenztypen

Neben den primitiven Datentypen gibt es in JavaScript Referenztypen, die komplexere Datenstrukturen ermöglichen:

// Object - Sammlung von Eigenschaften
let person = {
  name: "Max Mustermann",
  age: 30,
  isActive: true
};

// Array - Geordnete Liste von Werten
let colors = ["Rot", "Grün", "Blau"];

// Function - Ausführbarer Code
let greet = function(name) {
  return `Hallo ${name}`;
};

// RegExp - Reguläre Ausdrücke
let pattern = /^\d{5}$/;

// Date - Datums- und Zeitobjekte
let now = new Date();

// Map, Set, WeakMap, WeakSet (ES6)
let userRoles = new Map();
let uniqueIDs = new Set();

6.1.5 Typüberprüfung

Um den Typ einer Variable zu ermitteln, stehen verschiedene Methoden zur Verfügung:

// typeof-Operator
typeof 42;              // "number"
typeof "text";          // "string"
typeof true;            // "boolean"
typeof undefined;       // "undefined"
typeof null;            // "object" (historischer Fehler in JavaScript)
typeof {};              // "object"
typeof [];              // "object" (Arrays sind in JavaScript Objekte)
typeof function() {};   // "function"

// instanceof-Operator für Referenztypen
[] instanceof Array;    // true
new Date() instanceof Date; // true

// Array.isArray() für Arrays
Array.isArray([]);      // true

Die Besonderheit, dass typeof null den Wert "object" zurückgibt, ist ein bekannter Fehler in JavaScript, der aus Kompatibilitätsgründen nie behoben wurde.

6.1.6 Typkonvertierung (Type Coercion)

JavaScript führt in vielen Situationen automatische Typkonvertierungen durch, insbesondere bei Vergleichen und Operationen mit unterschiedlichen Datentypen.

6.1.6.1 Implizite Konvertierung

// String + Number = String
"5" + 3;            // "53"

// Boolean + Number = Number
true + 1;           // 2 (true wird zu 1 konvertiert)

// Boolean + String = String
true + "test";      // "truetest"

// Vergleiche mit ==
5 == "5";           // true (Wertgleichheit mit Typkonvertierung)
0 == "";            // true (leerer String wird zu 0 konvertiert)
0 == false;         // true (false wird zu 0 konvertiert)

Diese automatischen Konvertierungen können zu unerwartetem Verhalten führen. Daher ist es empfehlenswert, stets den strikten Vergleichsoperator === zu verwenden, der keine Typkonvertierung durchführt:

5 === "5";          // false (unterschiedliche Typen)
0 === "";           // false
0 === false;        // false

6.1.6.2 Explizite Konvertierung

Für präzisere Kontrolle über Typkonvertierungen bietet JavaScript verschiedene explizite Methoden:

// Zu String konvertieren
String(42);         // "42"
(42).toString();    // "42"
42 + "";            // "42" (implizite Konvertierung)

// Zu Number konvertieren
Number("42");       // 42
parseInt("42px");   // 42 (extrahiert Zahlen am Anfang)
parseFloat("3.14"); // 3.14
+"42";              // 42 (unärer Plus-Operator)

// Zu Boolean konvertieren
Boolean(1);         // true
Boolean(0);         // false
Boolean("");        // false
Boolean("text");    // true
!!42;               // true (doppelte Negation)

6.1.7 Truthy und Falsy Werte

In JavaScript werden Werte in booleschen Kontexten (z.B. in if-Anweisungen) automatisch in true oder false konvertiert:

// Falsy-Werte - werden zu false konvertiert
if (false) {}        // false
if (0) {}            // false
if ("") {}           // false
if (null) {}         // false
if (undefined) {}    // false
if (NaN) {}          // false

// Truthy-Werte - werden zu true konvertiert
if (true) {}         // true
if (42) {}           // true
if ("text") {}       // true
if ({}) {}           // true
if ([]) {}           // true
if (function() {}) {} // true

Diese Eigenschaft wird häufig für Kurzschlussauswertungen genutzt:

// Standardwert zuweisen, wenn Variable undefined ist
let username = userInput || "Gast";

// Funktion nur ausführen, wenn sie existiert
callback && callback();

6.1.8 Best Practices bei der Arbeit mit Typen

  1. Verwenden Sie === und !== statt == und !=, um unerwartete Typkonvertierungen zu vermeiden.
  2. Führen Sie explizite Typkonvertierungen durch, um Ihre Absicht klar zu dokumentieren.
  3. Seien Sie vorsichtig bei mathematischen Operationen mit Strings und setzen Sie ggf. explizite Konvertierungen ein.
  4. Achten Sie auf die Besonderheiten von NaN: NaN === NaN ist false, verwenden Sie stattdessen isNaN() oder besser Number.isNaN().
  5. Für präzisere Zahlenvergleiche bedenken Sie die Fließkommaarithmetik: 0.1 + 0.2 !== 0.3.
// Probleme mit Fließkommazahlen
console.log(0.1 + 0.2);         // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3);  // false

// Lösungsansatz für präzise Vergleiche
function isApproximatelyEqual(a, b, epsilon = 0.0001) {
  return Math.abs(a - b) < epsilon;
}

console.log(isApproximatelyEqual(0.1 + 0.2, 0.3));  // true