mitmario.dev

Spread und Rest

JavaScript Im Browser 3 Min Lesezeit 4 BeispieleLektion 6 von 9

Drei Punkte, zwei Bedeutungen, und dazwischen liegt der Unterschied zwischen einem Programm, das man versteht, und einem, das man debuggen muss. Die Schreibweise kennst du schon halb: der Rest-Parameter aus Lektion 5.5 ist eine ihrer beiden Hälften.

Auseinander und zusammen

Dieselben drei Punkte, zwei Bedeutungen
// Rechts vom Gleichheitszeichen verteilen sie: aus einer Liste
// werden einzelne Werte.
const zahlen = [3, 9, 1];
console.log("Als Liste:", zahlen);
console.log("Verteilt:", Math.max(...zahlen));

// Ohne die drei Punkte bekäme Math.max die ganze Liste als ein
// einziges Argument und wüsste damit nichts anzufangen.
console.log("Ohne Punkte:", Math.max(zahlen));

// Links sammeln sie, das ist der Rest-Parameter aus Lektion 5.5.
function summe(...werte) {
  return werte.reduce((a, b) => a + b, 0);
}
console.log("Gesammelt:", summe(3, 9, 1));

// Merksatz: rechts auseinander, links zusammen.

Die drei Punkte bedeuten je nach Stelle zwei verschiedene Dinge, und es lohnt sich, das einmal sauber zu trennen.

Rechts vom Gleichheitszeichen verteilen sie, also dort, wo ein Wert gebraucht wird. Math.max(...zahlen) macht aus der Liste drei einzelne Argumente. Das nennt sich Spread, und ohne ihn bekäme Math.max die ganze Liste als ein Argument und antwortet mit NaN.

Links vom Gleichheitszeichen sammeln sie, also dort, wo ein Name steht. Das ist der Rest aus Lektion 5.5: function summe(...werte) packt alle Argumente in eine Liste.

Der Merksatz dazu ist kurz: rechts auseinander, links zusammen. Beides schreibt sich gleich, weil es dieselbe Idee von zwei Seiten ist.

Ein Objekt kopieren

Kopieren und dabei etwas ändern
const original = { titel: "JS", dauer: 47, frei: true };

// Eine Kopie: die Eigenschaften werden einzeln übernommen.
const kopie = { ...original };
console.log("Kopie:", kopie);

// Beim Kopieren gleich etwas überschreiben. Der letzte gewinnt,
// deshalb steht die Änderung hinter dem Spread.
const laenger = { ...original, dauer: 50 };
console.log("Geändert:", laenger);

// Davor stehend hätte sie keine Wirkung.
const wirkungslos = { dauer: 50, ...original };
console.log("Falsch herum:", wirkungslos);

// Und das Original ist unangetastet.
console.log("Original:", original);

// Zwei Objekte zusammenführen geht genauso.
const standard = { frei: false, sprache: "Deutsch" };
console.log("Zusammen:", { ...standard, ...original });

{ ...original } legt ein neues Objekt an und übernimmt jede Eigenschaft einzeln. Danach hast du zwei Objekte, und Änderungen am einen lassen das andere in Ruhe. Genau das war in Lektion 7.2 das Problem.

Schreibst du hinter den Spread noch ein Paar, überschreibt es die kopierte Eigenschaft. Der letzte gewinnt, und deshalb ist die Reihenfolge nicht egal: { dauer: 50, ...original } bringt gar nichts, weil der Spread die 50 gleich wieder überschreibt.

Für den, der genau hinschaut: die überschriebene Eigenschaft behält ihre Position. dauer bleibt an zweiter Stelle und rutscht nicht ans Ende, obwohl sie hinten notiert ist. Das ist selten wichtig und in Lektion 7.7 doch einmal sichtbar.

Zwei Objekte zusammenzuführen ist derselbe Handgriff. Damit baut man Standardwerte: erst die Vorgaben, dann die tatsächlichen Werte darüber.

Und dasselbe für Listen

Listen aneinanderhängen
const alt = ["HTML", "CSS"];
const neu = ["JavaScript", "Node"];

// Aneinanderhängen, ohne eine der beiden zu verändern.
console.log("Zusammen:", [...alt, ...neu]);

// Etwas vorn oder hinten dazu.
console.log("Vorn:", ["Grundlagen", ...alt]);
console.log("Hinten:", [...alt, "Fertig"]);

// Eine Kopie der Liste, damit push das Original in Ruhe lässt.
const kopie = [...alt];
kopie.push("Neu");
console.log("Kopie:", kopie);
console.log("Original:", alt);

// Zum Vergleich: ohne Kopie trifft es das Original.
const kein = alt;
kein.push("Aua");
console.log("Original danach:", alt);

Mit eckigen Klammern funktioniert es genauso. [...alt, ...neu] hängt zwei Listen aneinander, ohne eine der beiden anzufassen, und etwas Einzelnes darf einfach dazwischenstehen.

Der praktisch wichtigste Fall ist die Kopie vor einer verändernden Methode. push, sort und reverse aus Abschnitt 6 arbeiten auf dem Original. Ein [...liste] davor macht daraus eine Kopie, und das Original bleibt, wie es war.

Die letzten Zeilen des Beispiels zeigen den Fall ohne Kopie, und er ist der Grund für die ganze Lektion. const kein = alt legt keine Liste an, es legt einen zweiten Namen für dieselbe Liste an. Ein push darauf trifft das Original, und wer das nicht erwartet, sucht lange.

Wo die Kopie aufhört

Die Kopie ist flach
const original = {
  titel: "JS",
  autor: { name: "Mario" },
};

const kopie = { ...original };

// Die obere Ebene ist wirklich getrennt.
kopie.titel = "CSS";
console.log("Titel original:", original.titel);
console.log("Titel Kopie:", kopie.titel);

// Eine Ebene tiefer nicht. Beide zeigen auf dasselbe Objekt.
kopie.autor.name = "Jemand anders";
console.log("Autor original:", original.autor.name);
console.log("Autor Kopie:", kopie.autor.name);

// structuredClone kopiert wirklich alles.
const echt = structuredClone(original);
echt.autor.name = "Noch jemand";
console.log("Nach structuredClone:", original.autor.name);

Jetzt die Einschränkung, und sie ist wichtig genug für den Merksatz dieser Lektion.

Die Kopie ist flach. Kopiert wird nur die oberste Ebene. Steht in einer Eigenschaft ein weiteres Objekt, wird nicht dieses Objekt kopiert, sondern nur der Verweis darauf. Original und Kopie zeigen danach auf dasselbe innere Objekt, und eine Änderung daran ist an beiden Stellen sichtbar.

Das Beispiel zeigt beides nebeneinander: der Titel ist wirklich getrennt, der Autor nicht. Diese Sorte Fehler ist besonders unangenehm, weil sie nur manchmal auftritt, nämlich genau dann, wenn die Daten eine Ebene tief genug sind.

Wer wirklich alles kopieren will, nimmt structuredClone. Das ist eine Funktion des Browsers, kein Teil der Sprache, und sie kann fast alles außer Funktionen.

Warum man überhaupt kopiert statt zu ändern, ist die eigentliche Antwort dieser Lektion. Eine Änderung an einer Stelle, die an einer ganz anderen auftaucht, ist der Fehlertyp, den man am schwersten findet. Wer stattdessen eine geänderte Kopie zurückgibt, hat diese Klasse von Fehlern gar nicht erst.

Zum Mitnehmen

Die Kopie ist flach. Was eine Ebene tiefer liegt, teilen sich Original und Kopie weiterhin, und Änderungen daran tauchen an beiden Stellen auf.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

Was in dieser Lektion steckt

  • Artikel mit 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe im Editor, direkt im Browser geprüft

    Öffnet sich mit dem Basis Konto.