Abschnitt 19 · Lektion 2
Adressen und Parameter
Eine Adresse ist Text, und genau deshalb behandelt man sie besser nicht als Text.
URL zerlegt für dich
<p>Eine lange Adresse und alles, was drinsteckt.</p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
} // Der Port steht hier mit Absicht auf 8080. Bei 443 wirft der
// Browser ihn weg, weil er für https ohnehin gilt, und dann sähen
// hostname und host gleich aus.
const adresse = new URL(
"https://mitmario.dev:8080/lernen/javascript?kurs=js&frei=1#abschnitt-3"
);
console.log("protocol:", adresse.protocol);
console.log("hostname:", adresse.hostname);
console.log("host:", adresse.host);
console.log("port:", adresse.port);
console.log("pathname:", adresse.pathname);
console.log("search:", adresse.search);
console.log("hash:", adresse.hash);
console.log("origin:", adresse.origin);
// Eine relative Adresse braucht eine Basis.
const relativ = new URL("/lernen/css", "https://mitmario.dev");
console.log("Zusammengesetzt:", relativ.toString());
// Und eine kaputte Adresse wirft.
try {
new URL("/lernen/css");
} catch (fehler) {
console.warn("Ohne Basis geht es nicht:", fehler.message);
} new URL(...) nimmt eine Adresse und gibt dir ein Objekt, in dem jeder Bestandteil einzeln steht:
protocol ist https:, mit Doppelpunkt.
hostname ist der Servername ohne Port, host mit. Im Beispiel steht der Port auf 8080, sonst wäre der Unterschied nicht zu sehen: einen Standardport (443 bei https, 80 bei http) wirft der Browser weg, und dann liefern beide dasselbe.
pathname ist der Pfad, immer mit führendem Schrägstrich.
search ist alles ab dem Fragezeichen, hash alles ab dem Doppelkreuz.
origin ist die Kombination aus Protokoll, Server und Port. Das ist derselbe Begriff, der dir bei CORS in Lektion 16.5 begegnet ist.
Zwei Kleinigkeiten: Eine relative Adresse braucht eine Basis als zweites Argument. Und eine Adresse, die keine ist, führt zu einem Fehler statt zu einem stillen null. Wo du fremde Eingaben verarbeitest, gehört ein try/catch darum.
searchParams
<p>searchParams ist der bequeme Zugang zu allem nach dem Fragezeichen.</p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
} const adresse = new URL("https://mitmario.dev/suche?q=grid&stufe=neu&stufe=mittel");
console.log("q:", adresse.searchParams.get("q"));
console.log("stufe, erster Treffer:", adresse.searchParams.get("stufe"));
console.log("stufe, alle:", adresse.searchParams.getAll("stufe"));
console.log("Gibt es seite?", adresse.searchParams.has("seite"));
console.log("Fehlender Parameter:", adresse.searchParams.get("seite"));
// set ersetzt, append hängt an.
adresse.searchParams.set("seite", "2");
adresse.searchParams.append("stufe", "profi");
adresse.searchParams.delete("q");
console.log("Neu:", adresse.toString());
// Alles auf einmal als Objekt. Mehrfache Namen verlieren
// dabei ihre Zusatzwerte, deshalb nur bei einfachen Fällen.
console.log("Als Objekt:", JSON.stringify(Object.fromEntries(adresse.searchParams))); Alles nach dem Fragezeichen bekommst du über searchParams, und zwar bequem:
get(name) liefert den Wert oder null, wenn es ihn nicht gibt.
getAll(name) liefert alle Werte, denn ein Name darf mehrfach vorkommen.
has(name) beantwortet die Frage nach dem bloßen Vorhandensein.
set ersetzt, append hängt an, delete entfernt.
Wichtig dabei: Ein Parameterwert ist immer Text. Aus ?seite=2 kommt die Zeichenfolge 2, und wer damit rechnen will, wandelt sie um, genauso wie beim Browserspeicher in Lektion 17.1.
Object.fromEntries(adresse.searchParams) macht daraus in einer Zeile ein Objekt. Das ist bequem und verliert die Zusatzwerte mehrfach vorkommender Namen, taugt also nur für einfache Fälle.
Niemals von Hand zusammenbauen
<p>Derselbe Suchbegriff, einmal von Hand und einmal ordentlich.</p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
} const begriff = "grün & günstig";
const basis = "https://mitmario.dev/suche";
// Von Hand. Sieht harmlos aus und ist kaputt.
const vonHand = `${basis}?q=${begriff}&seite=2`;
console.log("Von Hand:", vonHand);
console.log("Der Server liest als q:", new URL(vonHand).searchParams.get("q"));
// Das Und-Zeichen im Wert hat einen neuen Parameter
// erfunden, den es nie geben sollte.
console.log("Und plötzlich gibt es:", [...new URL(vonHand).searchParams.keys()]);
// Ordentlich.
const richtig = new URL(basis);
richtig.searchParams.set("q", begriff);
richtig.searchParams.set("seite", "2");
console.log("Richtig:", richtig.toString());
console.log("Der Server liest als q:", richtig.searchParams.get("q"));
// Für einen einzelnen Wert außerhalb einer URL.
console.log("encodeURIComponent:", encodeURIComponent(begriff)); Das ist der eigentliche Grund für diese Lektion.
In einer Adresse haben bestimmte Zeichen eine feste Bedeutung: & trennt Parameter, = trennt Name und Wert, # beginnt den Anker, das Leerzeichen hat dort gar nichts zu suchen. Steht eines davon im Wert, muss es umgeschrieben werden.
Im dritten Beispiel siehst du, was sonst passiert: Aus einem Suchbegriff mit einem Und-Zeichen wird ein zusätzlicher Parameter, den nie jemand gemeint hat. Kein Fehler, keine Warnung, nur ein Ergebnis, das nicht stimmt.
searchParams.set erledigt das von selbst. Ein Leerzeichen wird dabei zum Pluszeichen, ein Umlaut zu einer Folge von Prozentzeichen, und beim Lesen kommt der ursprüngliche Text wieder heraus.
Für einen einzelnen Wert außerhalb einer URL, etwa in einem Pfadstück, gibt es encodeURIComponent. Das ist der Handweg für den Rest.
Wozu du das brauchst
Zwei Anwendungen begegnen dir ständig:
Eine Ansicht, die sich teilen lässt. Der gewählte Filter steht in der Adresse, und wer den Link weitergibt, gibt die Ansicht mit. Ohne Parameter landet der Empfänger auf der leeren Startansicht.
Ein fetch mit Filtern (Lektion 16.2). Du baust die Adresse mit searchParams zusammen und übergibst sie als Text an fetch.
Was hier geht und was nicht
Ein Hinweis zum Schluss, damit du nicht suchst. history.pushState funktioniert hier, und du kannst es in drei Zeilen selbst sehen. Öffne ein Beispiel und tipp in der Eingabezeile der Console:
history.pushState({ n: 1 }, "", "?seite=2") und danach history.pushState({ n: 2 }, "", "?seite=3"). In der Adresszeile über der Seite steht jetzt /index.html?seite=3, und der Zurück-Pfeil daneben ist nicht mehr grau.
Häng vorher einen Lauscher an: addEventListener("popstate", (e) => console.log("zurück zu", e.state)). Ein Klick auf Zurück schreibt dann zurück zu {n: 1} in die Console und die Adresszeile springt auf ?seite=2. Das ist genau das Verhalten, auf das eine teilbare Filteransicht aufbaut: Die Adresse ändert sich, ohne dass die Seite neu geladen wird, und der Zurück-Pfeil tut trotzdem, was der Nutzer erwartet.
Zwei Grenzen gehören dazu. history.state bleibt hier leer. Das Zustandsobjekt kommt im popstate an, aber die Abfrage dazwischen antwortet mit null; draußen steht dort, was du zuletzt hineingelegt hast.
Und location ist hier nichts wert. Lesen geht, die Antwort ist nur nicht zu gebrauchen: location.href sagt about:srcdoc, location.origin sagt null, und location.search ist leer, auch wenn in der Adresszeile ein Parameter steht. Setzen kannst du es ausprobieren, es kostet nur nichts: Die Seite navigiert dann wirklich weg, die Lektionsansicht holt sie dreimal zurück und schreibt dir dazu eine Zeile in die Console. Deine Ausgabe ist danach weg, und was du sehen wolltest, siehst du nicht.
Die Lektion arbeitet deshalb durchgehend mit URL-Objekten über feste Zeichenketten. Alles daran gilt unverändert für die echte Adresse. Im echten Browser liest du sie mit new URL(location.href), und im popstate-Handler nimmst du dort dasselbe: erst lesen, dann mit searchParams auseinandernehmen.
Zum Mitnehmen
Eine Adresse baut man nie mit Plus zusammen. Ein Leerzeichen, ein Umlaut oder ein Und-Zeichen im Wert, und sie stimmt nicht mehr.
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 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe im Editor, direkt im Browser geprüft
Öffnet sich mit dem Basis Konto.