atob 和 btoa 的名字容易讓人以為它們直接處理普通文本。瀏覽器中的實際約定是:在 Base64 文本和每個字元代表一個位元組的字串之間轉換。這個“位元組字串”不保證是可讀 Unicode 文本。
兩種含義不同的字串
JavaScript 字串採用 UTF-16 碼元。位元組字串則要求每個字元值在 0–255 範圍內。ASCII 字元符合要求,所以 btoa('Hello') 可以執行;中文超出一個位元組,btoa('你好') 會報錯。即使字元能放進一個位元組,也不代表使用了 UTF-8。例如 é 對應單位元組 E9,而 UTF-8 編碼需要 C3 A9。
console.log(btoa('é')); // 6Q==,單位元組 E9
console.log(btoa(String.fromCharCode(0xc3, 0xa9))); // w6k=,UTF-8
在位元組轉換前後處理 UTF-8
使用 TextEncoder 把文字變成 UTF-8 位元組,解碼後用帶 fatal: true 的 TextDecoder 還原文本。這樣遇到非法序列會明確失敗,而不是插入替換字元。
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); // 中文😊
二進位制不等於亂碼
atob('/w==') 得到 FF 位元組,它不是合法的單獨 UTF-8 字元,但仍然是合法二進位制。應通過 Hex 檢視檢查或按位元組下載。將它再次交給 TextEncoder,會把 U+00FF 編成另一組位元組,造成資料變化。
瀏覽器解碼器也不等於完整的協議校驗器。嚴格場景還需檢查字母表、填充和未使用位。大檔案不要使用 String.fromCharCode(...bytes) 一次展開所有位元組,這可能觸及函式引數數量限制。介面定義見 MDN: atob() 和 MDN: btoa()。