encodeURI vs. encodeURIComponent: Welche Funktion Sie wann verwenden
encodeURIComponent kodiert 11 Zeichen, die encodeURI unverändert lässt – und genau diese 11 Zeichen entscheiden, ob ein Wert in seinem Parameter bleibt.
JavaScript bringt zwei eingebaute Funktionen für die Prozentkodierung mit, und sie unterscheiden sich in genau einem Punkt: wie viel von der URL-Syntax sie unangetastet lassen. encodeURI geht davon aus, eine vollständige URL zu bekommen, und behält die Zeichen, die einer URL ihre Struktur geben, etwa /, ? und &. encodeURIComponent geht davon aus, ein einzelnes Datenstück zu bekommen, und kodiert auch diese Zeichen, damit die Daten nicht mit Struktur verwechselt werden können. Kurz gesagt: Um einen Wert in eine URL einzusetzen, verwenden Sie encodeURIComponent oder, besser noch, die URL-API.
Der genaue Unterschied
Beide Funktionen wandeln jedes Zeichen, das sie nicht unverändert lassen, in UTF-8 um und schreiben jedes Byte als % mit zwei hexadezimalen Ziffern in Großbuchstaben. Aus é wird so %C3%A9 und aus € wird %E2%82%AC. Der einzige Unterschied ist die Menge der druckbaren ASCII-Zeichen, die jede Funktion unverändert lässt:
| Zeichen | encodeURI | encodeURIComponent |
|---|---|---|
A-Z a-z 0-9 - _ . ! ~ * ' ( ) | Bleiben unverändert | Bleiben unverändert |
# $ & + , / : ; = ? @ | Bleiben unverändert | Werden kodiert |
Leerzeichen und " % < > [ \ ] ^ ` { | } | Werden kodiert | Werden kodiert |
| Steuerzeichen und alles außerhalb von ASCII | Als UTF-8-Bytes kodiert | Als UTF-8-Bytes kodiert |
Auf die mittlere Zeile kommt es an. Diese 11 Zeichen trennen die Bestandteile einer URL. Bleiben sie innerhalb eines Werts unkodiert, ändert sich, was die URL bedeutet:
const s = 'Tom & Jerry/2?';
encodeURI(s); // 'Tom%20&%20Jerry/2?'
encodeURIComponent(s); // 'Tom%20%26%20Jerry%2F2%3F'
Wann Sie welche Funktion verwenden
Verwenden Sie encodeURIComponent für alles, was Sie in eine URL einsetzen: den Wert oder Namen eines Query-Parameters, ein einzelnes Pfadsegment, ein Fragment oder eine ganze URL, die als Parameter übergeben wird, etwa bei redirect=. Das passiert, wenn Sie stattdessen die andere Funktion nehmen:
const q = 'rock & roll';
// Wrong: the & starts a new parameter
encodeURI('https://example.com/search?q=' + q);
// 'https://example.com/search?q=rock%20&%20roll'
// The server sees q = 'rock ' and a second parameter ' roll'
// Right
'https://example.com/search?q=' + encodeURIComponent(q);
// 'https://example.com/search?q=rock%20%26%20roll'
encodeURI ist für den engen Fall gedacht, dass Sie bereits eine vollständige, korrekt aufgebaute URL haben, die lediglich Leerzeichen oder Zeichen außerhalb von ASCII enthält, etwa https://example.com/café?name=Zoë. Selbst dann lauern zwei Fallen:
- Die Funktion kodiert
%. Eine URL, die bereits teilweise kodiert ist, wird doppelt kodiert:encodeURI('https://example.com/a%20b')lieferthttps://example.com/a%2520b. - Die Funktion kodiert
[und]. Diese Klammern umschließen IPv6-Adressen, daher wird aushttp://[::1]:8080/die Zeichenkettehttp://%5B::1%5D:8080/, und das ist kein gültiger Host mehr.
Wenn Sie eine URL aus Einzelteilen zusammensetzen, ist new URL() meist das bessere Werkzeug: Es kodiert jeden Teil passend zu der Stelle, an der er steht.
const url = new URL('https://example.com/search');
url.searchParams.set('q', 'café & thé');
url.href; // 'https://example.com/search?q=caf%C3%A9+%26+th%C3%A9'
Der Vergleich mit RFC 3986
RFC 3986 definiert zwei Zeichengruppen. Die nicht reservierten Zeichen (Abschnitt 2.3) müssen nie kodiert werden: Buchstaben, Ziffern und - . _ ~. Die reservierten Zeichen (Abschnitt 2.2) haben eine syntaktische Funktion: die allgemeinen Trennzeichen : / ? # [ ] @ und die Sub-Delimiter ! $ & ' ( ) * + , ; =.
Keine der beiden JavaScript-Funktionen hält sich exakt daran. encodeURIComponent lässt fünf Sub-Delimiter unkodiert: ! ' ( ) *. Browsern und den meisten Servern ist das egal, Systemen, die Signaturen über die kodierte URL berechnen, aber nicht. Signaturen nach OAuth 1.0 (RFC 5849) und AWS Signature Version 4 verlangen beide, dass jedes Zeichen außer den nicht reservierten kodiert wird. Ein Wert mit Apostroph oder Klammern kann deshalb dazu führen, dass die Signatur nicht übereinstimmt. Die übliche Lösung:
function encodeRFC3986(str) {
return encodeURIComponent(str).replace(/[!'()*]/g,
c => '%' + c.charCodeAt(0).toString(16).toUpperCase());
}
encodeRFC3986("it's (fine)!*"); // 'it%27s%20%28fine%29%21%2A'
URLSearchParams und das Pluszeichen
URLSearchParams und damit auch url.searchParams verwendet keine der beiden Funktionen. Es folgt dem Format application/x-www-form-urlencoded, das HTML-Formulare beim Absenden benutzen, und das unterscheidet sich in zwei Punkten: Ein Leerzeichen wird zu +, und nur Buchstaben, Ziffern und * - . _ bleiben unverändert, also werden auch ! ' ( ) ~ kodiert.
encodeURIComponent('a b+c'); // 'a%20b%2Bc'
new URLSearchParams({ q: 'a b+c' }).toString(); // 'q=a+b%2Bc'
Beide Ergebnisse sind in einem Query-String korrekt. Ärger gibt es, wenn die kodierende und die dekodierende Seite sich nicht einig sind. decodeURIComponent macht aus + kein Leerzeichen, daher liefert decodeURIComponent('a+b%2Bc') das Ergebnis a+b+c, während new URLSearchParams('q=a+b%2Bc').get('q') das Ergebnis a b+c zurückgibt. Dekodieren Sie Query-Strings mit URLSearchParams, oder ersetzen Sie + durch ein Leerzeichen, bevor Sie decodeURIComponent aufrufen. In einem Pfad ist + einfach ein Pluszeichen.
decodeURI vs. decodeURIComponent
Die Dekodierfunktionen spiegeln die Kodierfunktionen. decodeURIComponent dekodiert jede Escape-Sequenz. decodeURI lässt die Escape-Sequenzen der Zeichen stehen, die encodeURI gar nicht kodiert hätte, also # $ & + , / : ; = ? @, damit das Dekodieren einer URL ihre Struktur nicht verändert: decodeURI('%C3%A9%20%2F%3F') liefert é %2F%3F, während decodeURIComponent das Ergebnis é /? zurückgibt.
Beide werfen URIError: URI malformed (so lautet die V8-Meldung in Chrome und Node.js), wenn auf ein % keine zwei Hex-Ziffern folgen, wie in 100%, oder wenn die Bytes kein gültiges UTF-8 sind, wie bei %E9 aus einem System, das é als Latin-1 kodiert hat. Die Kodierfunktionen werfen denselben Fehler bei einer einzelnen Hälfte eines Surrogatpaars, etwa bei einem String, der mitten in einem Emoji abgeschnitten wurde.
escape() ist keine Alternative
escape() ist eine Altlast, die nur noch für alten Code existiert. Sie lässt @ * _ + - . / unverändert und verwendet kein UTF-8: Zeichen bis U+00FF werden zu einem einzelnen Latin-1-Byte, alles darüber zu einer nicht standardkonformen %u-Sequenz. escape('café €') liefert caf%E9%20%u20AC, was Server, die UTF-8 erwarten, nicht dekodieren können, und decodeURIComponent('%E9') wirft einen Fehler. Ersetzen Sie die Funktion durch encodeURIComponent und unescape() durch decodeURIComponent. Denken Sie aber daran, dass bereits im alten Format gespeicherte Daten konvertiert werden müssen.
Doppelte Kodierung
Jede Kodierfunktion kodiert auch das % selbst, und zwar als %25. Wer eine Funktion zweimal anwendet, erhält deshalb Sequenzen wie %2520: encodeURIComponent(encodeURIComponent('a b')) liefert a%2520b. Kodieren Sie Rohwerte genau einmal, und zwar in dem Moment, in dem sie in die URL eingefügt werden, und kodieren Sie nie einen String, den Sie bereits kodiert erhalten haben. Der einzige legitime Fall ist eine URL innerhalb einer anderen URL: https://example.com/?q=a%20b als Parameter kodiert ergibt korrekt ...q%3Da%2520b, und die empfangende Seite dekodiert einmal, um die innere URL zurückzubekommen.
Um beide Modi mit Ihrer eigenen Eingabe zu vergleichen, bietet der URL Encoder eine Auswahl zwischen encodeURIComponent und encodeURI. URL decodieren dekodiert schon während der Eingabe und behandelt + wie bei Formulardaten als Leerzeichen. Ein Wert mit einem echten Pluszeichen sollte es daher als %2B enthalten.