Tutorials › Teil 2: Daten

Kapitel 8 von 16

Objekte: Daten mit Namen

Ein Tarif hat einen Anbieter, einen Preis, eine Geschwindigkeit und eine Laufzeit. Als Array wäre das eine Zumutung:

const tarif = ["Telekom", 9.95, 16, 24];

Was war noch mal Position zwei? Preis oder Geschwindigkeit? Man muss raten oder nachschauen.

const tarif = {
  anbieter: "Telekom",
  preis: 9.95,
  mbit: 16,
  laufzeit: 24,
};

zeige(tarif.anbieter, "kostet", tarif.preis, "Euro.");

Statt Positionen gibt es Namen. Das ist der ganze Unterschied.

Zwei Wege hinein

const tarif = { anbieter: "o2", preis: 14.99 };

zeige(tarif.anbieter);
zeige(tarif["anbieter"]);

const feld = "preis";
zeige(tarif[feld]);

Der Punkt ist die normale Schreibweise. Die eckigen Klammern brauchst du, wenn der Name selbst in einer Variablen steckt oder wenn er Zeichen enthält, die im Punktzugriff nicht erlaubt sind.

Was passiert bei einem Namen, den es nicht gibt?

const tarif = { anbieter: "o2" };

zeige(tarif.preis);
zeige("Gibt es preis?", "preis" in tarif);

undefined, kein Absturz. Das ist bequem und gleichzeitig tückisch, weil man Tippfehler in Feldnamen nicht sofort bemerkt.

Ändern, hinzufügen, löschen

const tarif = { anbieter: "o2", preis: 14.99 };

tarif.preis = 12.99;
tarif.technik = "DSL";
delete tarif.anbieter;

zeige(tarif);

Auch das geht mit const, aus demselben Grund wie beim Array.

Verschachtelung

Objekte dürfen Objekte und Arrays enthalten, in beliebiger Tiefe.

const ort = {
  name: "Flensburg",
  bundesland: "Schleswig-Holstein",
  einwohner: 92550,
  ausbau: { dsl: 90, kabel: 92, glasfaser: 99 },
  anbieter: ["Telekom", "Vodafone", "nordischnet"],
};

zeige(ort.ausbau.glasfaser, "% Glasfaser");
zeige("erster Anbieter:", ort.anbieter[0]);
zeige("Anzahl Anbieter:", ort.anbieter.length);

So sehen die Daten aus, aus denen auf dieser Seite die viertausend Ortsseiten unter Internetanbieter gebaut werden. Nichts Geheimnisvolles, nur verschachtelte Objekte.

Wenn eine Ebene fehlt

const ort = { name: "Greven" };

zeige("Name:", ort.name);
zeige("Glasfaser:", ort.ausbau?.glasfaser);
zeige("mit Ersatzwert:", ort.ausbau?.glasfaser ?? "unbekannt");

Ohne das Fragezeichen würde die zweite Zeile abstürzen: ort.ausbau ist undefined, und darin nach glasfaser zu suchen, ist ein Fehler. Das Fragezeichen bricht die Kette ab und liefert undefined, sobald etwas davor fehlt.

Diese Schreibweise heißt optionale Verkettung. Sie erspart Konstruktionen wie if (ort && ort.ausbau && ort.ausbau.glasfaser), die man in älterem Code überall sieht.

Zusammen mit ?? aus Kapitel 4 ergibt das eine sehr kompakte Art, mit unvollständigen Daten umzugehen.

Auspacken

const tarif = { anbieter: "Vodafone", preis: 19.99, mbit: 50 };

const { anbieter, preis } = tarif;
zeige(anbieter, preis);

const { mbit: geschwindigkeit } = tarif;
zeige("umbenannt:", geschwindigkeit);

const { laufzeit = 24 } = tarif;
zeige("mit Vorgabe:", laufzeit);

Das nennt sich Destructuring. Es holt einzelne Felder in eigene Variablen, kann sie dabei umbenennen und Vorgabewerte setzen.

Besonders praktisch ist es bei Funktionsparametern:

function beschreiben({ anbieter, preis, mbit }) {
  return `${anbieter}: ${mbit} MBit/s für ${preis}`;
}

zeige(beschreiben({ anbieter: "PYUR", preis: 23, mbit: 50 }));

Beim Aufruf steht dann sichtbar da, welches Argument was bedeutet. Bei beschreiben("PYUR", 23, 50) müsste man die Reihenfolge auswendig können.

Durchgehen

Ein Objekt ist keine Liste, for...of funktioniert nicht direkt. Es gibt drei Hilfsmittel:

const ausbau = { dsl: 90, kabel: 92, glasfaser: 99 };

zeige(Object.keys(ausbau));
zeige(Object.values(ausbau));

for (const [technik, quote] of Object.entries(ausbau)) {
  zeige(`${technik}: ${quote} %`);
}

Object.entries liefert ein Array aus Paaren. Zusammen mit dem Destructuring in der Schleifenkopfzeile ergibt das eine gut lesbare Zeile.

Kopie oder Verweis

Hier kommt der Punkt, an dem viele stolpern.

const original = { preis: 10 };
const zweiter = original;

zweiter.preis = 999;

zeige("original:", original.preis);

zweiter ist keine Kopie. Es ist ein zweiter Zettel mit derselben Adresse. Wer über den einen etwas ändert, ändert es für beide.

Bei Zahlen und Text passiert das nicht, die werden tatsächlich kopiert. Bei Objekten und Arrays wird nur der Verweis kopiert.

const original = { preis: 10, ausbau: { dsl: 90 } };

const flach = { ...original };
flach.preis = 999;
zeige("nach flacher Kopie:", original.preis);

flach.ausbau.dsl = 1;
zeige("aber die Ebene darunter:", original.ausbau.dsl);

Die drei Punkte machen eine Kopie, aber nur eine Ebene tief. Verschachtelte Objekte bleiben geteilt.

Für eine wirklich vollständige Kopie gibt es seit einigen Jahren eine eingebaute Funktion:

const original = { preis: 10, ausbau: { dsl: 90 } };
const echt = structuredClone(original);

echt.ausbau.dsl = 1;
zeige("Original bleibt:", original.ausbau.dsl);

Funktionen im Objekt

Ein Feld darf eine Funktion enthalten. Dann heißt es Methode.

const zaehler = {
  stand: 0,
  hoch() {
    this.stand++;
    return this.stand;
  },
  zuruecksetzen() {
    this.stand = 0;
  },
};

zeige(zaehler.hoch());
zeige(zaehler.hoch());
zaehler.zuruecksetzen();
zeige("nach Reset:", zaehler.stand);

this zeigt auf das Objekt, über das die Methode aufgerufen wurde. Bei Pfeilfunktionen funktioniert das nicht, die bringen kein eigenes this mit. Für Methoden also die Kurzschreibweise mit hoch() benutzen, nicht hoch: () => {}.

this hat in JavaScript einen zweifelhaften Ruf, und der ist verdient. Solange du es nur so einsetzt wie hier, ist es harmlos.

JSON

Objekte lassen sich in Text verwandeln und wieder zurück. Das ist das Format, in dem so gut wie alle Schnittstellen im Web ihre Daten schicken.

const ort = { name: "Flensburg", einwohner: 92550, glasfaser: true };

const text = JSON.stringify(ort);
zeige(text);
zeige("Typ:", typeof text);

const zurueck = JSON.parse(text);
zeige("wieder Objekt:", zurueck.einwohner);

Für lesbare Ausgabe nimmt stringify zwei weitere Argumente:

const ort = { name: "Flensburg", ausbau: { dsl: 90, glasfaser: 99 } };
zeige(JSON.stringify(ort, null, 2));

JSON kennt keine Funktionen, kein undefined und kein Datum. Alles davon geht beim Umwandeln verloren oder wird zu Text. Ein Date kommt als Zeichenkette zurück und muss von Hand wieder aufgebaut werden.

Was Text alles kann, steht im nächsten Kapitel.