Dlaczego atob() zwraca bajty, a btoa() odrzuca jakiś tekst

Zrozumienie ciągów binarnych JavaScript, błędów zakresu znaków i kroku UTF-8 potrzebnego w przypadku atob i btoa.

Imiona atob i btoa może sugerować zwykłą konwersję tekstu. Ich umowa przeglądarki jest węższa: konwertują między tekstem Base64 a ciągiem znaków, którego wartości reprezentują pojedyncze bajty. Ten ciąg bajtów jest kontenerem, a nie obietnicą, że wynikiem będzie czytelny tekst Unicode.

Dwa różne ciągi

Ciągi JavaScript używają jednostek kodu UTF-16. Ciąg bajtów celowo ogranicza wartość każdego znaku do 0–255. btoa('Hello') działa, ponieważ wartości ASCII mieszczą się w tym zakresie. btoa('你好') kończy się niepowodzeniem, ponieważ te znaki nie mieszczą się w jednym bajcie. Nawet pasujący znak może wprowadzać w błąd: btoa('é') koduje pojedynczy bajt E9, a nie UTF-8 bajtów C3 A9 oczekiwanych przez wiele usług.

console.log(btoa('é')); // 6Q== (byte E9)
console.log(btoa(String.fromCharCode(0xc3, 0xa9))); // w6k= (UTF-8)

Umieść konwersję UTF-8 wokół funkcji bajtowych

TextEncoder generuje UTF-8 bajtów z tekstu. Po dekodowaniu Base64, błąd krytyczny TextDecoder konwertuje prawidłowe UTF-8 bajty z powrotem na tekst i odrzuca nieprawidłowe sekwencje, zamiast je po cichu zastępować. Ten mały przykład jest celowo wyraźny, aby granica była widoczna.

const text = '中文😊';
const bytes = new TextEncoder().encode(text);
let binary = '';
for (const byte of bytes) binary += String.fromCharCode(byte);
const encoded = btoa(binary);
console.log(encoded); // 5Lit5paH8J+Yig==
const decodedBytes = Uint8Array.from(atob(encoded), c => c.charCodeAt(0));
const decoded = new TextDecoder('utf-8', { fatal: true }).decode(decodedBytes);
console.log(decoded); // 中文😊

Nie traktuj wyniku binarnego jako uszkodzonego ciągu

atob('/w==') reprezentuje bajt FF. Sam w sobie nie jest poprawny UTF-8, ale są to całkowicie poprawne dane binarne. Prawidłową następną akcją może być pobranie bajtów lub widok szesnastkowy, a nie inna sztuczka konwersji tekstu. Przepuszczanie tego ciągu bajtów TextEncoder zakoduje znak U+00FF i zmieni bajty.

Dekoder przeglądarki również nie jest kompletnym walidatorem protokołu. Akceptowane białe znaki i pominięte dopełnienie mogą różnić się od rygorystycznych wymagań aplikacji. Jeśli protokół wymaga kodowania kanonicznego, sprawdź oddzielnie alfabet, dopełnienie i nieużywane bity. W przypadku dużych plików unikaj rozprzestrzeniania pełnej tablicy bajtów String.fromCharCode(...bytes); limity liczby argumentów mogą nie działać na długo przed tym, zanim plik stanie się niezwykle duży.

Przeczytaj umowy dotyczące przeglądarki w MDN: atob() i MDN: btoa(). Aplikacja powinna zdecydować, czy chce konwersji zezwalającej, ścisłego walidatora czy przepływu pracy zachowującego pliki, zamiast zakładać, że jest to to samo zadanie.