Abschnitt 7 · Lektion 8
Synthese: der Datensatz
Ein Objekt allein kommt selten vor. Was aus einer Datenbank kommt, aus einer Datei oder in Abschnitt 16 aus dem Netz, ist fast immer eine Liste aus Objekten: viele Einträge derselben Form, jeder mit denselben Feldern. Diese Lektion bringt Abschnitt 6 und Abschnitt 7 an genau dieser Form zusammen.
Die Form, die überall vorkommt
<h1>Ausgabe</h1>
<p>Was dieses Beispiel tut, steht in der Console.</p> // Erfundene Buchungen, wie sie aus einer Datenbank kommen könnten.
const buchungen = [
{ name: "Ida", kurs: "HTML", betrag: 120, monat: "01" },
{ name: "Ben", kurs: "CSS", betrag: 150, monat: "01" },
{ name: "Mila", kurs: "JS", betrag: 250, monat: "02" },
];
// Schritt eins: einen einzigen Eintrag ansehen.
console.log("Ein Eintrag:", buchungen[0]);
// Schritt zwei: die Felder benennen.
console.log("Felder:", Object.keys(buchungen[0]));
// Schritt drei: erst jetzt die Liste anfassen.
console.log("Wie viele:", buchungen.length);
console.log("Namen:", buchungen.map((b) => b.name)); Ein Array aus Objekten vereint beide Ordnungsprinzipien: außen zählt die Position, innen der Name. Das ist kein Zufall, sondern genau die Form, in der eine Tabelle aussieht, wenn man sie in Daten übersetzt. Jede Zeile ein Objekt, jede Spalte ein Schlüssel.
Bevor du eine Zeile Auswertung schreibst, sieh dir einen einzigen Eintrag an. Das ist keine Zeitverschwendung, sondern spart den größten Teil der späteren Fehlersuche. Du willst wissen, wie die Felder heißen, ob sie Zahlen oder Texte enthalten und ob sie überall vorhanden sind.
Dafür reichen zwei Zeilen in der Console: der erste Eintrag und seine Schlüssel. Erst danach fasst du die Liste als Ganzes an. Wer diesen Schritt überspringt, schreibt eine Kette über b.preis, obwohl das Feld betrag heißt, und bekommt eine Liste voller undefined, ohne dass irgendwo ein Fehler steht.
Eine Frage, eine Kette
<h1>Ausgabe</h1>
<p>Was dieses Beispiel tut, steht in der Console.</p> const buchungen = [
{ name: "Ida", kurs: "HTML", betrag: 120, monat: "01" },
{ name: "Ben", kurs: "CSS", betrag: 150, monat: "01" },
{ name: "Mila", kurs: "JS", betrag: 250, monat: "02" },
{ name: "Tom", kurs: "CSS", betrag: 200, monat: "02" },
];
// Eine Frage, eine Kette. Alle Buchungen über 150.
const teuer = buchungen.filter((b) => b.betrag > 150);
console.log("Über 150:", teuer.map((b) => b.name));
// Nur ein Kurs, dann sortiert, dann als Text.
const cssZeilen = buchungen
.filter((b) => b.kurs === "CSS")
.sort((a, b) => b.betrag - a.betrag)
.map((b) => `${b.name}: ${b.betrag}`);
console.log("CSS:", cssZeilen);
// Summieren.
const gesamt = buchungen.reduce((summe, b) => summe + b.betrag, 0);
console.log("Gesamt:", gesamt); Jede Auswertung ist eine Frage an die Liste, und jede Frage wird eine Kette aus den Methoden von Abschnitt 6.
Die Reihenfolge ist dabei fast immer dieselbe: erst wegwerfen, dann ordnen, dann umformen. filter zuerst, weil alles Weitere dann weniger zu tun hat und die Kette kürzer zu lesen ist. sort danach. map zuletzt, denn nach dem Umformen sind die ursprünglichen Felder weg, und danach lässt sich nicht mehr sinnvoll filtern.
Denk an Lektion 6.8: sort verändert die Liste, auf der es arbeitet. Nach einem filter ist das unkritisch, denn filter hat ohnehin eine neue Liste gebaut. Steht sort als erstes in der Kette, sortierst du die Ausgangsdaten mit.
Das Muster, das du wiedererkennen wirst
<h1>Ausgabe</h1>
<p>Was dieses Beispiel tut, steht in der Console.</p> const buchungen = [
{ kurs: "HTML", betrag: 120 },
{ kurs: "CSS", betrag: 150 },
{ kurs: "HTML", betrag: 80 },
{ kurs: "CSS", betrag: 200 },
];
// Das Muster: Startwert ist ein leeres Objekt, der Schlüssel
// kommt aus dem Eintrag, und ?? 0 fängt den ersten Treffer ab.
const umsatz = buchungen.reduce((stand, b) => {
stand[b.kurs] = (stand[b.kurs] ?? 0) + b.betrag;
return stand;
}, {});
console.log("Umsatz:", umsatz);
// Dasselbe Muster zum Zählen: statt betrag einfach 1.
const anzahl = buchungen.reduce((stand, b) => {
stand[b.kurs] = (stand[b.kurs] ?? 0) + 1;
return stand;
}, {});
console.log("Anzahl:", anzahl);
// Und zum Sammeln: statt einer Zahl eine Liste.
const nachKurs = buchungen.reduce((stand, b) => {
stand[b.kurs] = [...(stand[b.kurs] ?? []), b.betrag];
return stand;
}, {});
console.log("Gesammelt:", nachKurs); Gruppieren ist die eine Auswertung, für die es keine fertige Methode gibt, und deshalb ist es der Hauptgrund, reduce überhaupt zu lernen.
Das Muster hat immer dieselben drei Teile. Der Startwert ist ein leeres Objekt. Der Schlüssel kommt aus dem Eintrag. Das ?? 0 fängt den ersten Treffer ab, denn beim ersten Mal gibt es die Gruppe noch nicht, und undefined + 120 wäre NaN.
Ist das einmal verstanden, sind alle Varianten dieselbe Zeile mit anderem Inhalt: + b.betrag summiert, + 1 zählt, und mit [...(stand[b.kurs] ?? []), b.betrag] sammelst du statt zu rechnen.
Eine Anmerkung zu Lektion 7.6, weil sie sich zu widersprechen scheinen: hier wird der Zwischenstand verändert statt kopiert. Das ist in Ordnung, denn das Objekt gehört diesem reduce allein. Es wurde als Startwert angelegt, niemand außerhalb kennt es, und am Ende wird es zurückgegeben. Die Warnung aus 7.6 gilt fremden Objekten, nicht dem eigenen Zwischenstand.
Und der beste Eintrag
<h1>Ausgabe</h1>
<p>Was dieses Beispiel tut, steht in der Console.</p> const umsatz = { HTML: 300, CSS: 450, JS: 600 };
// Der Weg über die Paare: reduce ohne Startwert, der erste
// Eintrag ist der erste Kandidat.
const bestesPaar = Object.entries(umsatz).reduce((bisher, paar) =>
paar[1] > bisher[1] ? paar : bisher
);
console.log("Bestes Paar:", bestesPaar);
console.log("Nur der Name:", bestesPaar[0]);
// Mit Destructuring liest es sich besser.
const [name, wert] = bestesPaar;
console.log(`${name} mit ${wert}`);
// Der kurze Weg, wenn nur der Höchstwert zählt.
console.log("Höchster Wert:", Math.max(...Object.values(umsatz)));
// Aber Vorsicht: der liefert die Zahl, nicht den Namen dazu. Die letzte Frage einer Auswertung lautet oft „und welcher ist der größte”. Für den bloßen Zahlenwert reicht Math.max mit dem Spread aus Lektion 7.6. Meistens willst du aber den Namen dazu, und den liefert Math.max nicht.
Der Weg dorthin ist reduce über die Paare, diesmal ohne Startwert: der erste Eintrag ist der erste Kandidat, und jeder weitere verdrängt ihn, wenn er größer ist. Genau die Form aus Lektion 6.7, nur dass der Zwischenstand hier ein Paar ist und keine Zahl.
Ohne Startwert bricht reduce bei einer leeren Liste ab. Bei einer Auswertung, die aus echten Daten kommt, gehört deshalb ein Gedanke an den leeren Fall dazu. In Abschnitt 14 steht, wie man ihn sauber behandelt, statt zu hoffen.
Zum Mitnehmen
Array aus Objekten ist die Form, in der Daten praktisch immer ankommen. Wer einen Eintrag verstanden hat, kann die ganze Liste auswerten.
Jetzt du
Basis Konto, kostenlosZu 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.