Werte und Referenzen
Auftrag
Martina Hostettler meldet einen Fehler. Der Verein hat zwei weitere Zelte für vier Personen gekauft, und ein Freiwilliger hat sie mit einer kleinen Funktion in den Katalog eingetragen.
Seit die neuen Zelte im Katalog sind, passiert etwas Seltsames. Leiht jemand eines davon aus, steht das andere auch als ausgeliehen da. Dabei haben wir zwei.
Die Akzeptanzkriterien lauten:
- Jedes Zelt ist im Katalog ein eigener Eintrag mit einer eigenen Nummer.
- Wer ein Zelt ausleiht, ändert nur dieses eine Zelt.
- Die Teile eines Zelts, etwa die Heringe, gehören nur zu diesem Zelt.
Lernziele
Nach diesem Kapitel kannst du
- erklären, wie sich primitive Werte und Objekte beim Zuweisen unterscheiden,
- Fehler finden, bei denen zwei Variablen auf dasselbe Objekt zeigen,
- Objekte und Arrays mit Spread flach und mit
structuredClonetief kopieren, - Objekte vergleichen und Daten ändern, ohne das Original zu verändern.
Werte und Verweise
Abschnitt betitelt „Werte und Verweise“Weist du eine Zahl einer zweiten Variablen zu, bekommt diese eine Kopie des Werts. Bei einem Objekt kopiert JavaScript dagegen nur den Verweis darauf, auf Englisch die Referenz. Beide Variablen zeigen danach auf dasselbe Objekt.
let count = 3let copy = countcopy += 1console.log(count, copy)
const tent = { name: 'Zelt für vier Personen', availableFrom: null }const sameTent = tentsameTent.availableFrom = '2026-11-14'console.log(tent.availableFrom, tent === sameTent)copy += 1 ändert nur copy, count bleibt 3. sameTent ist dagegen kein zweites Zelt, sondern ein zweiter Name für dasselbe Objekt. Was du über den einen Namen änderst, siehst du über den anderen.
So verhalten sich alle Objekte, also auch Arrays, Datumswerte und Funktionen. Kopiert werden nur die primitiven Werte, Strings, Zahlen, Wahrheitswerte, null und undefined.
Objekte in Funktionen
Abschnitt betitelt „Objekte in Funktionen“Eine Funktion bekommt beim Aufruf dieselben Verweise wie die Variablen, die du übergibst. Sie kann ein übergebenes Objekt deshalb ändern.
function markAsLent(item) { item.availableFrom = '2026-11-14'}
function shout(name) { name = name.toUpperCase()}
const drill = { name: 'Bohrmaschine', availableFrom: null }const name = drill.namemarkAsLent(drill)shout(name)console.log(drill.availableFrom, name)markAsLent ändert das Objekt, auf das auch drill zeigt. shout weist ihrem Parameter name dagegen einen neuen Wert zu. Das ändert nur die Variable in der Funktion, die Variable name ausserhalb bleibt, wie sie war. Eine Funktion, die übergebene Objekte ändert, hat eine Nebenwirkung, die beim Aufruf niemand sieht. Solche Funktionen sind die häufigste Quelle für Fehler wie den mit den Zelten.
Der Fehler mit den Zelten
Abschnitt betitelt „Der Fehler mit den Zelten“Die Funktion des Freiwilligen sieht harmlos aus. Lies sie und sag voraus, was die beiden Zeilen am Schluss ausgeben.
const items = []
// The volunteer's function adds a thing as often as the association owns itfunction addCopies(items, thing, count) { for (let copy = 0; copy < count; copy += 1) { thing.id = items.length + 1 items.push(thing) }}
addCopies(items, { name: 'Zelt für vier Personen', availableFrom: null }, 2)items[0].availableFrom = '2026-11-14'
console.log(items[1].availableFrom, items[0] === items[1])console.log(items[0].id, items[1].id)Die Schleife schiebt zweimal dasselbe Objekt ins Array. items[0] und items[1] sind ein einziges Zelt mit zwei Plätzen in der Liste. Darum ist nach dem Ausleihen von items[0] auch items[1] ausgeliehen. Auch die Nummern verraten den Fehler. Der zweite Durchgang hat die Nummer des einen Objekts überschrieben, beide Plätze zeigen 2. Für jedes Zelt braucht es ein eigenes Objekt.
Der Spread-Operator ... überträgt alle Felder eines Objekts in ein neues Objekt. Bei Arrays überträgt er alle Elemente in ein neues Array.
const tent = { name: 'Zelt', availableFrom: null, parts: ['Heringe', 'Gestänge'] }
const copy = { ...tent }copy.availableFrom = '2026-11-14'copy.parts.push('Plane')console.log(tent.availableFrom, tent.parts.length)
const deepCopy = structuredClone(tent)deepCopy.parts.pop()console.log(tent.parts.length, deepCopy.parts.length)Nach { ...tent } hat die Kopie ihr eigenes Feld availableFrom, das Ausleihen der Kopie lässt das Original unverändert. Das Array parts hat Spread aber nicht kopiert, nur den Verweis darauf. Original und Kopie teilen sich die Teile, darum hat das Original danach drei. Spread kopiert flach, nur die oberste Ebene.
structuredClone kopiert tief, mit allen Objekten und Arrays darin, auch mit Datumswerten, Map und Set. Funktionen kann structuredClone nicht kopieren und meldet dann einen Fehler, und bei Objekten einer Klasse geht die Klasse verloren. Für Daten wie den Katalog, die als JSON vom Server kommen, passt es.
Vergleichen
Abschnitt betitelt „Vergleichen“=== vergleicht bei Objekten die Verweise, nicht den Inhalt.
const first = { id: 2, name: 'Zelt' }const second = { id: 2, name: 'Zelt' }
console.log(first === second, first.id === second.id)console.log([1, 2] === [1, 2], 'Zelt' === 'Zelt')console.log(JSON.stringify(first) === JSON.stringify(second))Zwei Objekte mit gleichem Inhalt sind nicht gleich, zwei gleiche Strings schon. Dinge im Katalog vergleichst du deshalb über ein Feld, das jedes Ding eindeutig macht, die Nummer id. Der Vergleich über JSON.stringify funktioniert hier, ist aber unzuverlässig, weil er von der Reihenfolge der Felder abhängt und Datumswerte zu Text macht.
Ändern ohne Nebenwirkung
Abschnitt betitelt „Ändern ohne Nebenwirkung“Statt ein Ding im Katalog zu ändern, kann eine Funktion eine neue Liste zurückgeben, in der nur das geänderte Ding ein neues Objekt ist. map aus dem nächsten Kapitel baut dafür eine neue Liste aus der alten.
const items = [ { id: 1, name: 'Bohrmaschine', availableFrom: null }, { id: 2, name: 'Zelt für vier Personen', availableFrom: null },]
// Returns a new list, only the lent thing is a new object, the original stays as it wasfunction lend(items, id, until) { return items.map((item) => (item.id === id ? { ...item, availableFrom: until } : item))}
const after = lend(items, 2, '2026-11-14')console.log(items[1].availableFrom, after[1].availableFrom)console.log(after === items, after[0] === items[0], after[1] === items[1])Die alte Liste bleibt, wie sie war. Die neue Liste übernimmt alle Dinge, die sich nicht geändert haben, als Verweis, nur das ausgeliehene Zelt ist ein neues Objekt. Das kostet wenig Speicher, und ein einziger Vergleich mit === zeigt, ob sich ein Ding geändert hat. React zum Beispiel erkennt Änderungen am Zustand genau so. Vue bemerkt zwar auch Änderungen an bestehenden Objekten, wie Teil 5 zeigt. Eine Funktion ohne Nebenwirkung bleibt trotzdem leichter zu verstehen und zu testen, weil sie nur über ihren Rückgabewert wirkt.
Im Code
Abschnitt betitelt „Im Code“Arbeite mit dem Beispiel des Freiwilligen. Lies zuerst den Code und schreib auf, was du erwartest.
const items = []
// The volunteer's function adds a thing as often as the association owns itfunction addCopies(items, thing, count) { for (let copy = 0; copy < count; copy += 1) { thing.id = items.length + 1 items.push(thing) }}
addCopies(items, { name: 'Zelt für vier Personen', availableFrom: null }, 2)items[0].availableFrom = '2026-11-14'
console.log(items[1].availableFrom, items[0] === items[1])console.log(items[0].id, items[1].id)- Was gibt
console.log(items.length)am Schluss aus? Prüfe es mit «Bearbeiten» und «Ausführen». - Ersetze
items.push(thing)durchitems.push({ ...thing, id: items.length + 1 })und lösche die Zeile davor. Was geben die beiden Zeilen jetzt aus? - Gib dem Zelt ein Feld
parts: ['Heringe']und hänge nach dem Aufruf beiitems[0].partsein weiteres Teil an. Was zeigtitems[1].parts? - Wie musst du die Zeile aus Schritt 2 ändern, damit auch die Teile nicht mehr geteilt werden?
Aufgaben
Abschnitt betitelt „Aufgaben“Die Aufgaben arbeiten mit den Funktionen des Freiwilligen. Du findest sie im Kursrepo im Ordner exercises/01-javascript/references/de. Kopiere den Ordner, installiere die Pakete und starte die Tests.
npm installnpm testAufgabe Einstieg
Korrigiere addCopies in src/catalog.js. Jede Kopie soll ein eigenes Objekt mit der nächsten freien Nummer sein, und das übergebene Ding soll unverändert bleiben. Die Tests in level-1.test.js prüfen das.
Lösung
Eine Zeile genügt, items.push({ ...thing, id: items.length + 1 }). Spread legt bei jedem Durchgang ein neues Objekt an und übernimmt alle Felder, id dahinter überschreibt die Nummer. Weil die Funktion thing.id nicht mehr setzt, bleibt das übergebene Ding unverändert. Die Lösung der Knobelei ersetzt die Zeile später durch eine tiefe Kopie.
Aufgabe Vertiefung
lend ändert das Ding im übergebenen Array. Schreib die Funktion so um, dass sie eine neue Liste zurückgibt und die alte Liste mit ihren Dingen unverändert lässt. Alle Dinge ausser dem ausgeliehenen sollen in der neuen Liste dieselben Objekte bleiben.
Lösung
// Returns a new list in which the thing is lent until the given date, the old list stays unchangedexport function lend(items, id, until) { return items.map((item) => (item.id === id ? { ...item, availableFrom: until } : item))}map erzeugt eine neue Liste. Für das gesuchte Ding entsteht mit Spread ein neues Objekt mit dem neuen Datum, alle anderen Dinge übernimmt map unverändert. Der dritte Test prüft genau das mit toBe, also mit ===.
Aufgabe Knobelei
Zelte haben Teile, etwa parts: ['Heringe', 'Gestänge']. Ändere addCopies so, dass jede Kopie ihre eigenen Teile hat und die Teile der Vorlage unverändert bleiben, wenn jemand bei einer Kopie ein Teil ergänzt oder entfernt.
Lösung
// Adds a thing as often as the association owns it, every copy gets the next free id.// structuredClone gives every copy its own parts, a spread would share them.export function addCopies(items, thing, count) { for (let copy = 0; copy < count; copy += 1) { items.push({ ...structuredClone(thing), id: items.length + 1 }) }}Mit Spread allein teilen sich alle Kopien dasselbe Array parts. structuredClone kopiert das Ding mit allen Arrays darin, danach setzt Spread die neue Nummer. Die Reihenfolge ist wichtig, id muss nach dem kopierten Ding stehen, sonst überschreibt die alte Nummer die neue.
Typische Fehler
Abschnitt betitelt „Typische Fehler“| Fehler | Was hilft |
|---|---|
const copy = original soll eine Kopie sein. | Mit { ...original } oder [...original] kopieren. |
| Nach einer Kopie mit Spread ändert sich ein Array oder Objekt darin auch im Original. | Spread kopiert flach. Für verschachtelte Daten structuredClone verwenden. |
sort, push, splice oder reverse ändern ein Array, das auch andere verwenden. | Mit einer Kopie arbeiten oder Methoden wie toSorted und toReversed nehmen, die ein neues Array liefern. |
Zwei Dinge werden mit === verglichen und sind nie gleich. | Die Nummern vergleichen, a.id === b.id. |
structuredClone meldet DataCloneError. | Das Objekt enthält eine Funktion oder einen anderen Wert, den es nicht kopieren kann. Nur die Daten kopieren. |
Ein KI-Assistent kopiert tief mit JSON.parse(JSON.stringify(...)). | structuredClone verwenden, das auch Datumswerte, Map und Set richtig kopiert. |
Selbstcheck
Abschnitt betitelt „Selbstcheck“Selbstcheck
- Was gibt
const a = { n: 1 }; const b = a; b.n = 2; console.log(a.n)aus, und warum? - Wann genügt eine Kopie mit Spread, und wann braucht es
structuredClone? - Eine Funktion bekommt ein Array und ruft darauf
sort()auf. Was merkt die Stelle, die sie aufgerufen hat? - Warum lässt sich mit
===schnell erkennen, ob sich ein Ding geändert hat, wenn Funktionen nie bestehende Objekte ändern?
Antworten
2.bzeigt auf dasselbe Objekt wiea, die Zuweisung hat nur den Verweis kopiert.- Spread genügt, wenn das Objekt nur primitive Werte enthält oder die inneren Objekte sich nicht ändern. Sobald Arrays oder Objekte darin geändert werden, braucht es
structuredClone. - Ihr Array ist danach sortiert.
sortändert das Array selbst, die Funktion hat eine Nebenwirkung. - Jede Änderung erzeugt ein neues Objekt. Ist ein Ding noch dasselbe Objekt wie vorher, hat es sich nicht geändert, ohne dass jemand die Felder einzeln vergleichen muss.
Auftrag
Die Zelte sind korrigiert, und Martina Hostettler bereitet die nächste Vorstandssitzung vor. Der Vorstand möchte wissen, wie viele Dinge es in jeder Kategorie gibt, und am Tresen fragen Freiwillige, welches Werkzeug gerade da ist. Für solche Auswertungen bringt JavaScript Methoden mit, die eine ganze Liste mit einer Funktion bearbeiten. Darum geht es im nächsten Kapitel.
Texte und Abbildungen stehen unter CC BY-SA 4.0, der Code unter der MIT-Lizenz und die Daten des Kurses unter CC0 1.0. Die Schrift der Abbildungen ist Kalam unter der SIL Open Font License 1.1. Die Quellen liegen auf GitHub.