Ratgeber · Encoding & Engine

TextEncoder und TextDecoder API: Wie der Browser Strings zu Bytes macht

binaerkonverter.de nutzt unter der Haube die TextEncoder- und TextDecoder-Klassen aus der Web-API. Wir gehen Schritt für Schritt durch, wie sie aufgebaut sind, was sie zurückgeben und wie du sie selbst nutzen kannst.

7 Min Lesezeit 1.576 Wörter 5 FAQs
Mateusz Viola
Mateusz ViolaBetreiber & Encoding-Tüftler
Geprüft am

Wenn du in einem modernen Browser einen String in Bytes umwandeln willst, brauchst du keine eigene Bit-Hexerei zu schreiben. Die Web-API bietet dafür zwei Klassen: TextEncoder und TextDecoder. Beide sind seit etwa 2014 in allen relevanten Browsern verfügbar, im WHATWG Encoding Standard spezifiziert und so simpel zu benutzen, dass sie in zwei Zeilen Code passen. binaerkonverter.de nutzt sie intensiv, weil sie schneller, korrekter und besser getestet sind als jede eigene Implementierung.

Was TextEncoder eigentlich macht

Der TextEncoder ist eine Klasse mit genau einer wichtigen Methode: encode(text). Sie nimmt einen String entgegen und gibt eine Uint8Array zurück, also ein typisiertes Byte-Array. Die Bytes sind die UTF-8-Codierung des Strings, byte-genau nach RFC 3629.

const encoder = new TextEncoder();
const bytes = encoder.encode('Hallo');
// bytes ist Uint8Array(5) [72, 97, 108, 108, 111]

Für den String “Hallo” sind das fünf Bytes, jedes ein ASCII-Wert. Für “Hä” wären es drei Bytes (H = 72, ä = C3 A4 = 195, 164). Für einen Smiley wie das grinsende Gesicht wären es vier Bytes (F0 9F 98 80 = 240, 159, 152, 128). Der Encoder kümmert sich automatisch um Multi-Byte-Sequenzen, Surrogate-Paare, BOM-Handling (keines im UTF-8-Standard) und Edge-Cases wie ungültige Surrogates.

Es gibt noch eine zweite Methode, encodeInto(text, uint8Array), die das Ergebnis direkt in ein vorallokiertes Buffer schreibt. Die ist nützlich für Performance-kritischen Code, der Allokationen vermeiden will. Für den Alltagsgebrauch reicht encode().

Wie TextDecoder den Weg zurück geht

TextDecoder macht die Umkehrung: Er nimmt Bytes und gibt einen String zurück.

const decoder = new TextDecoder('utf-8', { fatal: true });
const text = decoder.decode(new Uint8Array([72, 97, 108, 108, 111]));
// text ist "Hallo"

Anders als TextEncoder ist TextDecoder über sein Konstruktor-Argument konfigurierbar. Standard ist UTF-8, aber du kannst auch andere Encodings angeben: new TextDecoder('iso-8859-1'), new TextDecoder('windows-1252'), new TextDecoder('utf-16le'). Die unterstützten Encodings sind im WHATWG Encoding Standard gelistet, Browser implementieren typischerweise 40 bis 50 verschiedene.

Das zweite Argument ist ein Options-Objekt. Wichtigste Option: { fatal: true }. Damit wirft der Decoder eine TypeError-Exception, wenn er kaputte oder unvollständige Byte-Sequenzen findet. Im Default (fatal: false) ersetzt er die kaputten Bytes still mit dem Replacement-Character U+FFFD, was meistens nicht das ist was du willst. binaerkonverter.de nutzt grundsätzlich fatal: true, damit du eine klare Fehlermeldung bekommst statt eines Texts mit kryptischen Fragezeichen.

String "Hä"
<rect class="boxAccent" x="280" y="50" width="160" height="60" rx="8"/>
<text class="label" x="360" y="78">TextEncoder</text>
<text class="small" x="360" y="98">.encode()</text>

<rect class="box" x="500" y="50" width="160" height="60" rx="8"/>
<text class="label" x="580" y="78">Uint8Array</text>
<text class="small" x="580" y="98">[72, 195, 164]</text>

<path class="arrow" d="M 220 80 L 280 80"/>
<path class="arrow" d="M 440 80 L 500 80"/>

<rect class="box" x="60" y="160" width="160" height="60" rx="8"/>
<text class="label" x="140" y="188">String</text>
<text class="small" x="140" y="208">"Hä"</text>

<rect class="boxAccent" x="280" y="160" width="160" height="60" rx="8"/>
<text class="label" x="360" y="188">TextDecoder</text>
<text class="small" x="360" y="208">.decode() fatal</text>

<rect class="box" x="500" y="160" width="160" height="60" rx="8"/>
<text class="label" x="580" y="188">Uint8Array</text>
<text class="small" x="580" y="208">[72, 195, 164]</text>

<path class="arrowDown" d="M 500 190 L 440 190"/>
<path class="arrowDown" d="M 280 190 L 220 190"/>
String wird via TextEncoder zu Bytes, TextDecoder geht den umgekehrten Weg.

So baut binaerkonverter.de das ein

Die Engine hinter dem Tool ist im Kern ein paar Zeilen Code, die TextEncoder und TextDecoder zusammen mit ein bisschen Bit-Manipulation kombinieren. Der relevante Pseudocode sieht so aus:

function textToBinary(text, encoding, bitsPerGroup) {
  let bytes;
  if (encoding === 'utf-8') {
    bytes = new TextEncoder().encode(text);
  } else {
    // ASCII-Pfad mit strikter Prüfung
    bytes = new Uint8Array(text.length);
    for (let i = 0; i < text.length; i++) {
      const code = text.charCodeAt(i);
      if (code > 127) throw new Error(`Zeichen "${text[i]}" ist nicht ASCII-kompatibel`);
      bytes[i] = code;
    }
  }
  return Array.from(bytes)
    .map(b => b.toString(2).padStart(8, '0'))
    .join('');
}

Beim Rück-Weg läuft es analog: Die Binär-Sequenz wird in 8-Bit-Gruppen zerlegt, jede Gruppe zu einem Byte umgerechnet, die Bytes werden in einer Uint8Array gesammelt und dann via new TextDecoder(encoding, { fatal: true }).decode(bytes) zurück in einen String konvertiert. Wenn dabei eine kaputte UTF-8-Sequenz auftaucht (etwa weil der User nur halbe Bytes eingegeben hat), wirft der Decoder und das Tool zeigt eine klare Fehlermeldung.

Der fatal: true-Modus ist hier kein Detail, sondern der Kern der Verlässlichkeit. Ohne ihn würde das Tool kaputte Eingaben stillschweigend in Texte mit Replacement-Characters umwandeln, und der User würde sich fragen, woher die Fragezeichen kommen. Mit fatal: true bekommt er stattdessen eine klare Fehlermeldung wie “Diese Binär-Sequenz ist keine gültige UTF-8-Folge”.

Was TextEncoder NICHT macht

Ein paar typische Annahmen, die nicht stimmen, lohnt sich klarzustellen. Erstens: TextEncoder fügt keinen BOM (Byte Order Mark) hinzu. UTF-8 braucht laut RFC 3629 keinen BOM, weil es kein Byte-Ordering-Problem hat (Bytes sind Bytes, die Reihenfolge ist eindeutig). Manche Tools setzen trotzdem einen UTF-8-BOM (EF BB BF) an den Anfang, was aber gegen den Standard ist und in vielen Parsern Probleme macht. TextEncoder macht das richtig und lässt den BOM weg.

Zweitens: TextEncoder kann nicht in andere Encodings konvertieren. Wer Windows-1252-Bytes oder Shift-JIS-Bytes braucht, muss das manuell machen oder eine Library nutzen. Im Browser ist das selten ein Anwendungsfall, weil moderne Web-Standards alle UTF-8 verwenden. Für Legacy-Konvertierungen gibt es das npm-Paket iconv-lite, das in Node.js gut funktioniert.

Drittens: TextEncoder validiert nicht. Wenn du einen String mit ungültigen Unicode-Surrogaten reinwirfst (was in JavaScript möglich ist, weil Strings intern UTF-16-codepoint-Sequenzen sind, die nicht zwingend valide sein müssen), produziert TextEncoder trotzdem Bytes, aber die sind dann auch ungültiges UTF-8. Wer ganz sauber arbeiten will, prüft den String vorher mit String.prototype.isWellFormed() (in modernen Browsern verfügbar) oder iteriert mit dem Spread-Operator ([...str]), der unvollständige Surrogate als U+FFFD behandelt.

Beispiel: Eine eigene Konvertierungs-Routine

Wenn du selbst einen Mini-Konverter bauen willst, brauchst du genau diese fünf Zeilen:

function encodeUTF8(text) {
  return Array.from(new TextEncoder().encode(text))
    .map(b => b.toString(2).padStart(8, '0'))
    .join(' ');
}

function decodeUTF8(binary) {
  const bytes = new Uint8Array(
    binary.split(' ').map(b => parseInt(b, 2))
  );
  return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
}

encodeUTF8("Hä") gibt dir "01001000 11000011 10100100". decodeUTF8("01001000 11000011 10100100") gibt dir "Hä" zurück. Der Roundtrip funktioniert ohne Verlust, weil UTF-8 ein eindeutiges Encoding ist (jede valide Byte-Sequenz mapt auf genau einen String und umgekehrt).

Wenn du das selbst testen willst, öffne die Browser-Devtools (F12), geh in die Console und tipp die Funktionen ein. Du siehst sofort die Bytes für jedes Zeichen. Probier auch mal Sonderfälle: ein einzelnes ä, ein Emoji, ein japanisches Zeichen wie 漢. Du wirst sehen, dass die Byte-Anzahl je nach Zeichen variiert.

Performance-Charakteristik in der Praxis

Wer mit großen Textmengen arbeitet, profitiert von den nativ implementierten Encoder- und Decoder-Pfaden. Auf einem modernen Desktop-Browser verarbeitet TextEncoder typischerweise zwischen 500 MB/s und 2 GB/s, je nach CPU-Architektur und Eingabe-Charakteristik. Für reine ASCII-Eingaben sind die Encoder besonders schnell, weil keine Multi-Byte-Logik gebraucht wird. Bei viel CJK- oder Emoji-haltigen Eingaben fällt die Geschwindigkeit etwas ab, bleibt aber weit über jeder eigenen JavaScript-Implementierung.

Wer benchmarken will, kann das mit der Web-API performance.now() machen. Eine einfache Schleife, die 1.000-mal einen 10 KB großen Test-String encoded, gibt einen guten Durchsatz-Wert. Auf einem MacBook Pro M2 sind das typischerweise 0,5 bis 2 Millisekunden für 10 MB Datenmenge. Eine eigene JavaScript-Implementierung würde für die gleiche Datenmenge eher 20 bis 50 Millisekunden brauchen.

Für TextDecoder gilt Ähnliches. Im fatal-Mode ist er etwas langsamer als im Default-Mode, weil er pro Byte die Validität prüfen muss. Der Unterschied beträgt typischerweise 10 bis 20 Prozent, was bei den meisten Anwendungen vernachlässigbar ist. Im Gegenzug bekommst du saubere Fehler statt stiller Replacement-Characters, was den minimalen Performance-Aufwand wert ist.

Vergleich zu anderen Plattformen

Die Web-API TextEncoder und TextDecoder sind durch den WHATWG-Standard genau spezifiziert. Andere Plattformen haben ähnliche, aber nicht immer identische APIs. In Python ist das Äquivalent string.encode('utf-8') und bytes.decode('utf-8'). Beide funktionieren mit oder ohne strict-Mode (errors='strict' oder errors='replace').

In Go heisst die Pakete-Funktion []byte(string) für Encode und string([]byte) für Decode, mit ähnlichem Verhalten. Wer ungültige UTF-8-Sequenzen aktiv prüfen will, nutzt utf8.ValidString() aus dem unicode/utf8-Paket.

In Rust ist String per Definition immer valides UTF-8, was Konvertierungs-Fehler zur Compile-Zeit abfängt. str::from_utf8(&[u8]) gibt ein Result-Type zurück, was Fehlerbehandlung explizit macht. Das ist nach meiner Erfahrung der saubererste Ansatz, weil er die Sicherheit ins Type-System verlagert.

Was hängenbleibt

TextEncoder und TextDecoder sind die Standard-Werkzeuge für UTF-8-Konvertierung im Browser und in Node.js. TextEncoder kann nur UTF-8 produzieren, was eine bewusste WHATWG-Entscheidung ist. TextDecoder unterstützt mehrere Encodings und sollte immer mit { fatal: true } aufgerufen werden, damit kaputte Bytes klar diagnostizierbar sind statt still ein Replacement-Character zu produzieren. binaerkonverter.de nutzt beide Klassen intensiv und gibt dir bei kaputten Eingaben eine ehrliche Fehlermeldung statt einer halbgaren Konvertierung. Andere Plattformen wie Python, Go und Rust haben ähnliche APIs mit teils strengeren Verträgen, aber das Konzept bleibt überall das gleiche: String rein, Bytes raus, oder Bytes rein, String raus.

FAQ

Häufige Fragen

Warum gibt TextEncoder eine Uint8Array zurück und nicht einen String?

Weil Bytes keine Zeichen sind, und das Web-API will dich nicht in die Falle laufen lassen, die zwei zu verwechseln. Ein TextEncoder produziert die rohe Byte-Darstellung eines Strings nach UTF-8-Regeln. Diese Bytes sind keine textuellen Daten mehr, sondern numerische Werte zwischen 0 und 255. Uint8Array ist der passende JavaScript-Datentyp dafür: ein typisiertes Array mit unsigned 8-Bit-Integers, also genau einem Byte pro Eintrag. Wenn du die Bytes als Hex oder Binär darstellen willst, musst du sie selbst formatieren, etwa mit byte.toString(2).padStart(8, '0') für 8-Bit-Binärdarstellung. Genau das macht binaerkonverter.de intern.

Unterstützt TextEncoder auch andere Encodings als UTF-8?

Nein, TextEncoder ist hartcodiert auf UTF-8. Das ist eine bewusste Design-Entscheidung der WHATWG (Web Hypertext Application Technology Working Group). Andere Encodings wie UTF-16, Windows-1252 oder ISO-8859-1 sind veraltet oder nur für Legacy-Inhalte relevant. Wenn du wirklich Daten in einem anderen Encoding produzieren willst, musst du den Konvertierungsschritt selbst nachbauen, was nicht trivial ist. Für reine Lese-Operationen (Bytes zu String) gibt es TextDecoder, der über sein Konstruktor-Argument auch andere Encodings akzeptiert, etwa new TextDecoder('iso-8859-1'). Das macht historisch Sinn: Lesen muss Legacy-Inhalte vertragen, Schreiben sollte nur noch UTF-8 produzieren.

Was passiert ohne fatal: true bei kaputten UTF-8-Bytes?

Ohne fatal-Mode (also im Default) ersetzt TextDecoder kaputte oder unvollständige UTF-8-Sequenzen still mit dem Unicode-Replacement-Character U+FFFD. Das ist das Zeichen, das in Fonts oft als kleines Fragezeichen in einer Raute dargestellt wird. Klingt erstmal robust, ist aber gefährlich. Wenn dein Code irgendwo eine fehlerhafte Byte-Sequenz übergibt (defekte Übertragung, falsches Encoding angenommen, Buffer-Overflow), bekommst du einfach einen Text mit Fragezeichen statt einer klaren Fehlermeldung. In Bug-Reports gibt es dann das berüchtigte 'wieso steht da ein Fragezeichen, ich hab doch nur deutschen Text eingegeben'. Mit { fatal: true } wirft der Decoder stattdessen eine TypeError-Exception, die du im Code abfangen und sinnvoll behandeln kannst.

Wie schnell ist die TextEncoder-API im Vergleich zu manueller Codierung?

Deutlich schneller. TextEncoder ist in den meisten Browsern in nativen C-Code (V8 in Chrome, SpiderMonkey in Firefox, JavaScriptCore in Safari) implementiert und arbeitet mit hardware-optimierten SIMD-Pfaden. Eine manuelle JavaScript-Implementierung, die Codepoint für Codepoint durch den String iteriert und die UTF-8-Bit-Magic selbst macht, ist typischerweise 5 bis 20 Mal langsamer. Für kleine Strings (unter 1.000 Zeichen) merkst du das im Alltag nicht. Bei grösseren Datenmengen ab einem Megabyte aufwärts wird der Unterschied spürbar. binaerkonverter.de nutzt deshalb immer die Web-API, niemals eine eigene Routine. Wer mehr zur Performance der V8-Engine nachlesen will, findet bei den V8-Blog-Posts ausführliche Erklärungen.

Funktioniert TextEncoder auch in Node.js?

Ja, seit Node.js Version 11 ist TextEncoder global verfügbar, davor musste man ihn aus der util-Library importieren. Die Implementierung folgt dem WHATWG-Standard, also gleiche API und gleiches Verhalten wie im Browser. Das ist praktisch, weil du isomorphen Code schreiben kannst, der sowohl im Browser als auch auf dem Server läuft. In Deno und Bun ist TextEncoder ebenfalls global. Wenn du in einer Web-Worker- oder Service-Worker-Umgebung arbeitest, ist TextEncoder dort auch verfügbar. Einziger Sonderfall: ältere Internet-Explorer-Versionen (vor IE 11) kennen TextEncoder nicht, dort braucht es ein Polyfill. Im Jahr 2026 ist das aber kaum noch relevant, weil IE seit 2022 offiziell EOL ist.

Anzeige

Quellen

Worauf dieser Ratgeber sich stützt

Verwandte Ratgeber

Weiterlesen

Veröffentlicht · zuletzt geprüft
Verantwortlich: Mateusz Viola
Anzeige
Anzeige
Anzeige
Anzeige