Zum Inhalt springen

JavaScript - Daten & das DOM

SAP Implementation Consulting & ERP - Startupistan Deutschland · Block I · Lernnotizen zur Wiederholung.


Bisher hielten meine Variablen immer nur eine Sache auf einmal - einen Namen, einen Preis, ein Ja/Nein. In diesem Kapitel geht es darum, viele Dinge und strukturierte Dinge zu halten: Listen (Arrays), beschriebene Dinge (Objekte) und Baupläne zum Erzeugen von Objekten (Klassen). Danach bringt es sie über das DOM auf eine echte Webseite. Der rote Faden der ganzen Woche: Inhalt als Daten + eine wiederholbare Regel schlägt jedes Von-Hand-Kopieren.

Ein Array ist eine geordnete Liste von Werten, die unter einem Namen gespeichert ist. Ich schreibe es mit eckigen Klammern (ein Array-Literal) und lese die Einträge über ihren Index zurück - eine Positionsnummer, die bei 0 beginnt, nicht bei 1.

'use strict';
const flavors = ["rye", "spelt", "wheat"];
console.log(flavors[0]); // rye - erster Eintrag
console.log(flavors[2]); // wheat - letzter von dreien
console.log(flavors.length); // 3 - wie viele Einträge
flavors[1] = "sourdough"; // den zweiten Eintrag überschreiben
rye
wheat
3

Der wichtigste Unterschied überhaupt: Manche Methoden verändern das Array direkt (mutieren es), andere lassen es unangetastet und geben einen neuen Wert zurück. Diese zu verwechseln ist die Quelle böser Überraschungen.

MethodeTut wasMutiert?
push() / pop()Am Ende hinzufügen / entfernenJa
unshift() / shift()Am Anfang hinzufügen / entfernenJa
includes() / indexOf()Ist es vorhanden? / an welchem Index?Nein
slice(start, end)Ein Stück in ein neues Array kopieren (end ausgeschlossen)Nein
concat() / join(sep)Arrays verbinden / Einträge zu einem String zusammenklebenNein
const flavors = ["rye", "spelt", "wheat"];
flavors.push("olive"); // jetzt 4 Einträge, direkt verändert
console.log(flavors.pop()); // olive - entfernt & zurückgegeben
console.log(flavors.includes("rye")); // true
console.log(flavors.indexOf("wheat")); // 2
console.log(flavors.slice(0, 2)); // [ 'rye', 'spelt' ] - ein NEUES Array
console.log(flavors.join(" and ")); // rye and spelt and wheat

Eine Schleife führt einen Codeblock wiederholt aus. So ehre ich DRY (“Don’t Repeat Yourself”) über Daten, statt fast identische Zeilen von Hand zu schreiben.

Besucht jeden Eintrag direkt - kein Index-Buchhalten. Lies es als “für jedes flavor der flavors”.

const flavors = ["rye", "spelt", "wheat"];
for (const flavor of flavors) {
console.log(flavor);
}
// rye
// spelt
// wheat

Hier ist der Lohn - dieselbe Regel auf jeden Eintrag angewandt, kein Copy-Paste:

const artists = ["Pinkfong", "Asake", "Johnny Cash"];
for (const artist of artists) {
console.log(`=== ${artist} ===`);
console.log(`Listen to ${artist} now on every platform`);
}
=== Pinkfong ===
Listen to Pinkfong now on every platform
=== Asake ===
Listen to Asake now on every platform
=== Johnny Cash ===
Listen to Johnny Cash now on every platform

Füge dem Array einen vierten Künstler hinzu und eine vierte Karte erscheint gratis. Korrigiere den Satz einmal und jede Karte ist korrigiert. Das ist DRY in Aktion.

Diese Methoden nehmen einen Callback - eine Funktion, die ich übergebe und die die Methode einmal pro Eintrag ausführt. Die Arrow-Form ((x) => ...) hält jede davon auf einen lesbaren Einzeiler. Diese Tabelle über den Schreibtisch pinnen:

MethodeRückgabeVerwendung
forEachundefinedmit jedem Eintrag etwas tun (Seiteneffekt, z. B. ausgeben)
mapein neues Array, gleiche Längejeden Eintrag in etwas Neues umwandeln
filterein neues Array, meist kürzernur die Einträge behalten, die einen Test bestehen
reduceein einziger Wertdas ganze Array auf ein Ergebnis eindampfen
findein Eintrag (oder undefined)den ersten Eintrag holen, der einen Test besteht
some / everyein Booleanbesteht irgendein / bestehen alle Einträge einen Test?
const prices = [4.5, 12, 3.2, 8];
prices.forEach((p) => console.log(p)); // gibt jeden aus; gibt nichts zurück
const withTax = prices.map((p) => (p * 1.07).toFixed(2)); // ['4.82','12.84','3.42','8.56']
const cheap = prices.filter((p) => p < 5); // [ 4.5, 3.2 ]
const total = prices.reduce((sum, p) => sum + p, 0); // 27.7 (0 = Startwert)
const firstBig = prices.find((p) => p > 10); // 12
const anyBig = prices.some((p) => p > 10); // true
const allBig = prices.every((p) => p > 10); // false

Arrays und Objekte sind Referenztypen. Eine Variable hält nicht die Liste selbst - sie hält einen Pfeil (eine Referenz), der auf die eine Liste im Speicher zeigt. Eine schlichte Zuweisung kopiert also den Pfeil, nicht die Liste:

const original = ["rye", "spelt"];
const copy = original; // gleicher Pfeil → gleiche Liste!
copy.push("wheat");
console.log(original); // [ 'rye', 'spelt', 'wheat' ] ← das "Original" hat sich auch geändert

Zwei Namen, eine geteilte Liste - schreibe über den einen und beide sehen es. Der bewusste Weg, ein wirklich neues Array zu erzeugen, ist Spread-Syntax - drei Punkte innerhalb eines neuen Literals:

const original = ["rye", "spelt"];
const realCopy = [...original]; // brandneues Array mit denselben Einträgen
realCopy.push("olive");
console.log(realCopy.length); // 3
console.log(original.length); // 2 ← unangetastet

Ein Array beantwortet “welche Einträge, in welcher Reihenfolge?”. Ein Objekt beantwortet “was weiß ich über dieses eine Ding?”. Es ist eine Sammlung benannter Werte, geschrieben mit geschweiften Klammern (ein Objekt-Literal). Jedes Paar ist eine Eigenschaft: der Key links, der Wert rechts (jeder Typ - String, Zahl, Boolean, sogar ein weiteres Array oder Objekt).

const croissant = {
name: "Butter Croissant",
price: 2.8,
vegetarian: true,
};
console.log(croissant.name); // Butter Croissant - Punktnotation
const field = "price";
console.log(croissant[field]); // 2.8 - Klammernotation, Key aus einer Variable
croissant.price = 3.0; // eine Eigenschaft ändern

Arbeitsregel: Punkte wenn ich kann, Klammern wenn der Key in einer Variable liegt (oder Zeichen enthält, die ein einfacher Name nicht haben kann). Einen nicht existierenden Key zu lesen gibt still undefined zurück - ein vertippter Key ist der übliche Übeltäter.

Eine Methode ist einfach eine Eigenschaft, deren Wert eine Funktion ist. Innerhalb einer Methode verweist das Schlüsselwort this auf das Objekt, auf dem die Methode aufgerufen wurde - so liest eine Methode ihre eigenen Geschwister-Eigenschaften.

const croissant = {
name: "Butter Croissant",
price: 2.8,
describe: function () {
return `${this.name} costs ${this.price} euros`;
},
};
console.log(croissant.describe()); // Butter Croissant costs 2.8 euros

Setze beide zusammen und du erhältst die Form der meisten echten Daten: eine Liste von Dingen, jedes mit benannten Details. Jedes Array-Werkzeug funktioniert unverändert; innerhalb der Schleife antwortet jeder Eintrag auf Punktnotation.

const artists = [
{ name: "Pinkfong", genre: "Children's music", total: "11:31" },
{ name: "Adriano Celentano", genre: "Italian pop", total: "20:52" },
{ name: "Asake", genre: "Afrobeats", total: "14:08" },
];
for (const artist of artists) {
console.log(`${artist.name}, ${artist.genre}, ${artist.total} of music`);
}
const popActs = artists.filter((a) => a.genre.includes("pop"));
console.log(popActs.length); // 1

Ein Objekt hat keine Einträge, über die man loopen könnte - es hat Eigenschaften. Drei eingebaute Funktionen verwandeln seinen Inhalt in Arrays, und dann greift jedes Array-Werkzeug:

HelferRückgabe
Object.keys(obj)Array der Keys (Strings)
Object.values(obj)Array der Werte
Object.entries(obj)Array von [key, value]-Paaren
const hours = { saturday: "7-14", sunday: "8-12", monday: "closed" };
for (const [day, time] of Object.entries(hours)) {
console.log(`${day}: ${time}`);
}
// saturday: 7-14
// sunday: 8-12
// monday: closed

Es gibt auch for...in, das die Keys eines Objekts direkt besucht (for (const day in hours)). Es funktioniert und taucht im Code anderer Leute auf, aber ich bevorzuge den Weg über Object.keys / entries, damit ich im Array-Land mit einem einzigen mentalen Modell bleibe.

Destructuring zieht Eigenschaften (oder Array-Einträge) in einer Zeile in ihre eigenen Variablen. Objekte matchen über den Key-Namen; Arrays matchen über die Position.

const croissant = { name: "Butter Croissant", price: 2.8, vegetarian: true };
const { name, price } = croissant; // über den Key-Namen
console.log(`${name}, ${price} euros`); // Butter Croissant, 2.8 euros
const flavors = ["rye", "spelt", "wheat"];
const [first, second] = flavors; // über die Position
console.log(first, second); // rye spelt

Dieses [day, time] in der entries-Schleife oben war Array-Destructuring auf jedem Paar. Destructuring ist in modernen Frameworks überall - es lohnt sich, es jetzt flüssig lesen zu können. (Achte auf die Groß-/Kleinschreibung: const { Name } findet nichts und gibt undefined zurück.)

Wenn jedes Künstler-Objekt dieselben Keys und dieselbe describe-Methode trägt, wird das Kopieren dieser Struktur von Hand albern. Objektorientierte Programmierung (OOP) löst das: Beschreibe die Form einmal als Klasse (den Bauplan), fertige dann so viele Instanzen (konkrete Objekte), wie die Daten brauchen.

Das kompakte Beispiel zeigt das ganze Werkzeug - class, constructor, new, eine Methode, plus extends und super für Vererbung:

'use strict';
class Artist {
constructor(name, genre, total) { // läuft einmal pro neuer Instanz
this.name = name; // Daten auf DIESE Instanz setzen
this.genre = genre;
this.total = total;
}
describe() { // Klassenmethode - Kurzform, kein `function`
return `${this.name}, ${this.genre}, ${this.total} of music`;
}
}
class FeaturedArtist extends Artist { // Subklasse erbt alles von Artist
constructor(name, genre, total, blurb) {
super(name, genre, total); // ZUERST den Eltern-Konstruktor ausführen
this.blurb = blurb; // dann meine eigene Eigenschaft hinzufügen
}
describe() { // Override - auf der Elternversion aufbauen
return `${super.describe()}. Featured: ${this.blurb}`;
}
}
const cash = new Artist("Johnny Cash", "Country", "15:40");
const asake = new FeaturedArtist("Asake", "Afrobeats", "14:08", "Loudest new voice out of Lagos");
console.log(cash.describe());
console.log(asake.describe());
Johnny Cash, Country, 15:40 of music
Asake, Afrobeats, 14:08 of music. Featured: Loudest new voice out of Lagos
class = Bauplannew = eine Instanz bauenconstructor = Daten via this einrichtenextends = erbensuper() = Eltern aufrufen

Wenn der Browser eine HTML-Datei liest, behält er nicht den Text - er baut das DOM (Document Object Model): einen lebendigen Baum aus Knoten-Objekten im Speicher, einen pro Element, exakt so verschachtelt, wie das HTML verschachtelt. Die gerenderte Seite wird aus diesem Baum gezeichnet, deshalb der eine Satz, auf dem alles ruht: ändere den Baum, und die Seite ändert sich sofort auf dem Bildschirm.

HTML-Dateidie Zeichnung
→
DOM-Baumlebendige Objekte im Speicher
→
Gerenderte Seitewas der Besucher sieht
→
JS bearbeitet den BaumSeite aktualisiert sich sofort
Das HTML ist die Zeichnung des Architekten; das DOM ist das stehende Gebäude; JavaScript ist die Renovierungscrew mit den Schlüsseln.

Zuerst das Script mit der Seite verbinden. Diese Zeile kommt in den <head>; defer sagt dem Browser, die Datei erst nachdem das ganze Dokument geparst ist auszuführen, damit mein Code nicht nach Elementen greift, die noch nicht existieren:

<script src="app.js" defer></script>

document ist der Einstiegspunkt des Baums. querySelector(cssSelector) gibt das erste Element zurück, das auf einen CSS-Selektor passt (dieselben Selektoren wie in CSS - .class, #id, tag); querySelectorAll gibt alle Treffer als Liste zurück, über die ich loopen kann.

AufgabeWie
Ersten Treffer auswählendocument.querySelector(".tagline")
Alle Treffer auswählendocument.querySelectorAll(".card")
Text lesen / ändernel.textContent = "New text"
Klasse hinzufügen / entfernenel.classList.add("highlight") / .remove("highlight")
Inline-Style ändernel.style.color = "crimson"
'use strict';
const heading = document.querySelector("h1");
heading.textContent = "Startupistan Records"; // Baum ändert sich → Bildschirm ändert sich
heading.classList.add("highlight"); // CSS legt das Aussehen fest; JS entscheidet wann

Die Arbeitsteilung: CSS besitzt das Aussehen, JavaScript entscheidet wann ein Element eine Klasse trägt. Ein Selektor, der auf nichts passt, gibt still null zurück - denk an den Punkt für Klassen und die Raute für IDs.

Die Seite zu lesen ist die halbe Kraft; die andere Hälfte ist das Herstellen von Inhalt, der nie im HTML stand. Zwei Werkzeuge: createElement(tag) baut ein neues losgelöstes Element (es existiert nur im Speicher), und append() hängt es in ein Eltern-Element ein.

const artists = [
{ name: "Pinkfong", genre: "Children's music", total: "11:31" },
{ name: "Asake", genre: "Afrobeats", total: "14:08" },
];
const cardArea = document.querySelector(".cards");
for (const artist of artists) {
const card = document.createElement("article"); // die Karte bauen
const title = document.createElement("h3");
title.textContent = artist.name; // aus den Daten füllen
const line = document.createElement("p");
line.textContent = `${artist.genre}, ${artist.total} of music`;
card.append(title, line); // die Teile zusammensetzen
cardArea.append(card); // in den Baum einhängen
}

Lies den Schleifenkörper als Fließband: bauen → aus Daten füllen → zusammensetzen → einhängen. Füge dem Array einen dritten Künstler hinzu, lade neu, und eine dritte Karte erscheint. Dieses Daten-zu-Elementen-Muster ist das schlagende Herz jedes Frameworks, dem ich später begegnen werde.

Events und Formulare - ein durchgearbeitetes Beispiel

Abschnitt betitelt „Events und Formulare - ein durchgearbeitetes Beispiel“

Alles bisher läuft einmal beim Laden. addEventListener(eventName, callback) registriert eine Funktion, die jedes Mal läuft, wenn ein benanntes Event passiert. Übergib den Callback ungelaufen (keine Klammern) - () zu schreiben führt ihn einmal beim Laden aus und nie wieder.

Für Formulare zwei weitere Teile: Die value-Eigenschaft eines Inputs hält, was der Besucher getippt hat, und das submit-Event feuert, wenn er das Formular absendet. Ein absendendes Formular navigiert standardmäßig weg, also rufe ich event.preventDefault() auf dem Event-Objekt auf, um das zu stoppen und es an Ort und Stelle zu behandeln.

  1. Die Elemente greifen - einmal, ganz oben.

  2. Auf submit hören und die Standard-Navigation mit preventDefault() stoppen.

  3. input.value lesen, gegen leere Eingabe absichern (ein leerer String ist falsy), dann darauf reagieren.

'use strict';
const form = document.querySelector(".signup");
const nameInput = document.querySelector("#artist-name");
const list = document.querySelector(".cards");
form.addEventListener("submit", (event) => { // Callback UNGELAUFEN übergeben
event.preventDefault(); // die Seite bleibt stehen
const name = nameInput.value.trim(); // lesen, was getippt wurde
if (name) { // eine leere Absendung ablehnen
const card = document.createElement("article");
card.textContent = name;
list.append(card); // die neue Karte live hinzufügen
nameInput.value = ""; // das Feld für das nächste leeren
}
});
KernpunktKurz gemerkt
ArrayGeordnete Liste in einer Variable; Index beginnt bei 0, letzter ist length - 1
Mutierend vs. nichtpush/pop/shift/unshift verändern direkt; slice/map/filter/Spread geben neu zurück
Schleifenfor...of = jeder Eintrag (Standard); for = Index gebraucht; while = bedingungsgesteuert
DRYInhalt als Daten + eine Schleife ersetzt jede von Hand kopierte Zeile
forEach vs. mapforEach gibt undefined zurück (handelt); map gibt ein neues Array zurück (transformiert)
filter / find / reduceviele behalten / ersten Treffer holen / auf einen Wert eindampfen
ReferenztypenEin Array/Objekt zuzuweisen kopiert den Pfeil, nicht das Ding - nutze Spread [...x] / {...x} zum Kopieren
Flache KopieSpread kopiert nur die oberste Ebene; verschachtelte Referenzen bleiben geteilt (structuredClone für tief)
ObjektBenannte Werte (key: value); . wenn der Key literal ist, [var] wenn der Key in einer Variable steckt
Methode & thisMethode = Eigenschaft, die eine Funktion hält; this = das Objekt vor dem Punkt (keine Arrow)
keys/values/entriesVerwandeln ein Objekt in Arrays; loope entries mit einem [key, value]-Destructure
Destructuring{ name } = obj über den Key-Namen; [a, b] = arr über die Position
Class / newBauplan + new baut eine Instanz; constructor setzt Daten via this ein
extends / superextends erbt; super(...) ruft den Eltern-Konstruktor auf; super.m() ruft die Elternmethode auf
DOMLebendiger Baum aus Knoten-Objekten; ändere den Baum → Seite aktualisiert sich sofort
deferFührt das Script aus, nachdem das Dokument geparst ist, damit Auswahlen nicht null sind
Auswählen / ändernquerySelector (erster) / querySelectorAll (alle); textContent, classList, style
Erzeugen / appendcreateElement baut losgelöst; append hängt es ein - append vergessen = stilles Nichts
EventsaddEventListener("click", cb) führt cb jedes Mal aus; übergib den Callback ungelaufen
FormulareLies input.value; event.preventDefault() stoppt das Wegnavigieren beim Absenden