Man verfolgt einen Bug durch drei Dateien, nur um festzustellen, dass eine harmlose Helper-Funktion einen Benutzer umbenannt hat. Man hat das Original nie angefasst. Oder zumindest dachte man das. In JavaScript bedeutet die Zuweisung einer Variable an eine andere nicht immer das, was man erwartet. Die Sprache unterteilt ihre Daten in zwei Speicherstrategien, und das Vergessen, welche man gerade verwendet, ist der Grund, warum lautlose Mutationen in den Produktionscode schlüpfen.

Um dies zu verhindern, muss man den Unterschied zwischen primitiven Werten und Objekten genau so sehen, wie es die Engine tut.

Primitivtypen: Echte Kopien

Ein Primitivtyp ist ein einzelner, unteilbarer Datensatz. Er kann nicht in kleinere JavaScript-Werte zerlegt werden. Die Sprache definiert sieben primitive Typen: String, Number, Boolean, Undefined, Null, Symbol und BigInt.

Da Primitivtypen atomar sind, liegen sie im Allgemeinen direkt innerhalb der Variablenbindung. Wenn man eine primitive Variable auf eine andere kopiert, dupliziert die Engine die eigentlichen Daten. Jede Variable erhält ihren eigenen, unabhängigen Speicherplatz.

let a = "Alina";
let b = a;
b = "Ali";

console.log(a); // "Alina"
console.log(b); // "Ali"

Hier bleibt a unberührt. Die Neuzuweisung von b erzeugte einen brandneuen Wert und wies b darauf hin, während a immer noch seinen ursprünglichen String enthält. Dieses Verhalten nennt man „Copy by Value“. Es funktioniert identisch für Zahlen, Booleans, Symbole und den Rest der primitiven Familie. Man kann sie an Funktionen übergeben, neu zuweisen oder zurückgeben, ohne sich um Seiteneffekte auf benachbarte Variablen sorgen zu müssen.

Objekte: Gemeinsame Adressen

Objekte sind anders. Ein Objekt ist ein zusammengesetzter Container, der mehrere Datenelemente gruppiert. Diese Kategorie umfasst Plain Objects, Arrays, Funktionen, Dates und jeden anderen nicht-primitiven Typ. Da diese Strukturen groß und verschachtelt sein können, speichert JavaScript das gesamte Objekt nicht innerhalb der Variable. Stattdessen hält die Variable eine Referenz – im Wesentlichen eine Speicheradresse, die auf die an anderer Stelle gespeicherten tatsächlichen Daten zeigt.

Wenn man ein Objekt einer neuen Variable zuweist, kopiert die Engine die Adresse, nicht das Objekt. Zwei Variablen zeigen nun auf exakt dasselbe Haus.

const user = { name: "Alina" };
const copy = user;
copy.name = "Ali";

console.log(user.name); // "Ali"

Das Ändern von copy.name änderte auch user.name, da beide Namen auf dasselbe zugrunde liegende Objekt verweisen. Dies ist „Copy by Reference“. Die gleiche Überraschung tritt bei Arrays auf:

const scores = [82, 91, 74];
const backup = scores;
backup.push(88);

console.log(scores); // [82, 91, 74, 88]

Es gibt nur ein einziges Array im Speicher. scores und backup sind lediglich zwei Schilder, die darauf zeigen.

Das Schlüsselwort const sorgt für zusätzliche Verwirrung. Die Deklaration eines Objekts mit const sperrt die Variablenbindung, sodass man sie nicht auf eine neue Adresse zeigen lassen kann. Es sperrt jedoch nicht das Objekt selbst.

const settings = { theme: "dark" };
settings.theme = "light";        // Works perfectly.
settings = { theme: "dark" };    // TypeError

Entwickler erwarten oft, dass const Unveränderlichkeit garantiert. Das tut es nicht. Es verhindert lediglich die Neuzuweisung der Referenz. Wenn man den Inhalt versiegeln möchte, muss man die Kopie mit Absicht erstellen.

Wo die echten Bugs lauern

Referenz-Bugs treten selten als offensichtliche Variablen-Neuzuweisungen auf. Sie verstecken sich innerhalb von Funktionsaufrufen.

function addTimestamp(record) {
  record.timestamp = Date.now();
  return record;
}

const original = { id: 1 };
addTimestamp(original);

console.log(original.timestamp); // A number now exists here. Oops.

Der Parameter record erhielt eine Kopie der Referenz. Jede Mutation einer Eigenschaft innerhalb der Funktion schrieb direkt in das Objekt des Aufrufers. Die Funktion sah wie eine einfache Transformation aus, doch sie ließ Zustände über Scope-Grenzen hinweg durchsickern.

Dieses Muster ist besonders schmerzhaft in UI-Frameworks wie React, wo Zustandsaktualisierungen davon abhängen