繁體中文

開發者工具 · Base64 編碼器和解碼器

atob 和 btoa 代表什麼,以及為什麼它們只理解拉丁語-1

· 背景

base64 JavaScript 統一碼

btoa 和 atob 二進位字串位元組單元模型,與 Unicode 文字不同
原始 ToolAcre 向量圖

atob 和 btoa 來自 Netscape,名稱的意思是「ASCII 到二進位」和「二進位到 ASCII」。這篇文章介紹了它們來自哪裡、WHATWG 標準如何定義它們,以及為什麼它們從未學習過 Unicode。

一個讀起來像拼字錯誤的函數名稱-名稱造成的混亂和一行答案

atob 和 btoa 是 JavaScript 內建函數,於 20 世紀 90 年代在 Netscape 中引入。這些名稱都是縮寫:btoa 代表二進位到 ASCII,atob 代表 ASCII 到二進位。這些名稱反映了它們的年齡和設計:它們是在二進位表示位元組值字串 (0-255) 而不是更現代的 Uint8Array 或 Buffer 時構建的。通常為名稱提供的助記符不如可觀察的合約重要:一個函數將二進位字串對應到 Base64,另一個函數將其反轉。該儲存庫沒有記錄原始的命名決定,因此本文避免將民間傳說呈現為來源瀏覽器歷史記錄。

這些函數需要一個二進位字串:每個字元的程式碼單元必須在 0-255 範圍內,表示一個位元組。如果您傳遞一個代碼單元高於 255 的字元(如表情符號或來自拉丁語之外的重音字母 -1),則函數會拋出 InvalidCharacterError 或默默地產生不正確的輸出。 btoa(二進位到 ASCII)將二進位字串編碼為 base64。

名稱所暗示的內容以及位元組串合約實際證明的內容

輸入必須是字串,其中每個字元都是一個位元組(代碼單元 0-255)。 btoa(hello) 將 ASCII 位元組編碼為 base64 並傳回 aGVsbG8=。帶有字母 e-acute 的 btoa 似乎可以工作,因為預先組合的拉丁文-1 字母 e-acute (U+00E9) 的代碼單元為 233,位於 0-255 內。但是,btoa 將其編碼為單一位元組 0xE9,而不是 e-acute 應產生的 UTF-8 位元組 0xC3 0xA9。在類型化數組成為普通位元組容器之前,JavaScript API 使用其程式碼單元代表位元組的字串。模型仍然可見,因為 btoa 拒絕 255 以上的程式碼單元。這些檔案並未建立精確的產品年表;故障邊界是透過可執行測試建立的。

這種無聲損壞比錯誤更危險:結果看起來不錯,但實際上是錯誤的。 atob(ASCII 到二進位)將 Base64 解碼回二進位字串。 atob(aGVsbG8=) 回傳你好。輸出是一個二進位字串,其中每個字元的程式碼單元是 0-255,代表一個位元組。如果要將其轉換為正確的 Unicode 文字,則需要將位元組解釋為 UTF-8 並使用 TextDecoder 對其進行解碼。

遺留的二進位字串模型 - 可觀察的行為,無需未經驗證的瀏覽器歷史記錄宣告

對於 ASCII,這個額外的步驟是不必要的(ASCII 是 UTF-8 的子集),但對於任何非 ASCII 位元組,它是必需的。 atob 不做這種解釋;它以二進位字串形式傳回原始位元組。

WHATWG 標準(Web API 的現行標準)在 HTML 規格中定義了 atob 和 btoa。這個定義包括 atob 的寬容 Base64 解碼演演算法:它跳過空格並接受缺少的填充,從而使現實世界的 Base64(包括帶有換行符的 MIME 包裝的 Base64)可解碼。 ToolAcre 在呼叫瀏覽器解碼器之前對空格、URL 安全標點符號和缺失填充進行標準化。然後,它將傳回的程式碼單元複製到 Uint8Array 並套用致命的 UTF-8 解碼器。這種組合將寬容的 Base64 語法與嚴格的文字解釋分開。

此實作中的當前行為 - 允許字母規範化和嚴格的 UTF-8 文字解碼

函數簽章沒有改變,但標準定義是函數功能的權威。為什麼 atob 和 btoa 只接受拉丁文-1?因為當它們在 20 世紀 90 年代設計時,JavaScript 沒有直接表示位元組的方法(沒有 Uint8Array 或 ArrayBuffer)。將位元組傳遞給函數的唯一方法是作為字串,其中每個字元代表一個位元組。

這稱為二進位字串,並按照現代標準會令人困惑。 JavaScript 字串是 Unicode 文字,而不是位元組序列。此設計將兩者合併在一起:每個程式碼單元為 0-255 的字串是二進位字串。命名反映了時代:btoa 中的 ASCII 字面意思是 ASCII 文字的 7 位,但實現接受任何位元組 (0-255)。直接向 btoa 添加 Unicode 模式將改變其長期存在的位元組串契約並帶來相容性風險。相反,經過審查的來源在編碼之前組成了 TextEncoder。本文可以驗證該組成;它忽略了未記錄在儲存庫中的有關標準委員會動機的宣告。

工作範例:在帶有空格和缺少填充的字串上追蹤 forgiving-base64 — atob 接受嚴格解碼器拒絕的內容

現代替代方案避免了二進位字串模型。編碼 API 提供 TextEncoder 將文字轉換為 UTF-8 位元組,並提供 TextDecoder 將 UTF-8 位元組轉換回文字。

Base64 編碼和解碼現在在 HTML 規範中為字串(atob 和 btoa)和類型化陣列指定。 Base64 編碼器和解碼器工具在 atob 和 btoa 周圍使用 TextEncoder 和 TextDecoder,因此您可以安全地編碼和解碼 Unicode 文字,而不受 Latin-1 限制。帶有空格或未填充的值會成功,因為規範化會刪除空格並恢復所需的區塊長度。清理長度留下餘數為 1 的值在 atob 之前被拒絕。這種區別顯示了「寬容」在這裡的含義:接受可恢復的格式,而不接受結構上不可能的輸入。

較新的標準適用於類型化數組的 Base64 — 定性描述,並附有檢查當前瀏覽器支援的註釋

使用 btoa 處理 Unicode 需要先將文字編碼為 UTF-8 位元組。舊的解決方法是 btoa(unescape(encodeURIComponent(text))),它令人困惑但有效:encodeURIComponent 對 UTF-8 位元組進行百分比編碼,unescape 將三元組轉換回字元,btoa 對產生的二進位字串進行百分比編碼。這可行,但依賴已棄用的函數並難以閱讀。現代程式碼應該使用 TextEncoder(text).map(byte => String.fromCharCode(byte)) 後面跟著 btoa,或者更好的是,直接轉換為 Uint8Array 並使用 Encoding API。

Atob 不會自動為您提供文字;它給你二進位。 atob(Y2Fmw6kg8J+YgA==) 傳回一個二進位字串,其中包含帶有 caf 口音和表情符號的 UTF-8 編碼文字的位元組。若要恢復文字,請將二進位字串轉換為 Uint8Array 並將其傳遞給 TextDecoder(utf-8)。 Base64 編碼器和解碼器工具會自動執行此操作:您貼上文字,它會將其編碼為 UTF-8 位元組,然後編碼為 Base64。類型化數組 Base64 API 正在跨瀏覽器發展,但此來源並未使用它們。根據需要當前的兼容性檢查和後備計劃。 ToolAcre 的明確位元組數組轉換仍然是可檢查的,並由其當前的測試套件覆蓋。

類型化數組的替代方案正在不斷發展——在依賴它們之前驗證當前的瀏覽器支持

您貼上 base64,它會解碼為 UTF-8 字節,然後解碼為文字。中間二進位字串步驟被隱藏,因為它是 20 世紀 90 年代 API 的實作細節。了解 atob 和 btoa 對於偵錯遺留程式碼或使用為您提供二進位字串的舊 API 非常有用。大多數新程式碼應該完全避免二進位字串模型。

如果您需要對 Base64 進行編碼或解碼,Base64 編碼器和解碼器工具可以正確處理 Unicode。如果您正在建立 API,請接受 Uint8Array 或類型化陣列視圖,或清楚記錄您的 base64 是 UTF-8 還是 Latin-1。當在沒有 TextEncoder 的情況下查看使用 btoa 和非 ASCII 文字的程式碼時,這是一個錯誤:輸出編碼了錯誤的位元組。 Node Buffer 和非瀏覽器執行時期定義了不同的 API 和接受規則。他們被故意排除在外。本文中的宣告涉及瀏覽器原語和 apps/dev, 中實現的包裝器,而不是每個環境中名為 atob 或 btoa 的每個函數。

重點:兩個具有位元組字串契約的 90 年代函數 — Base64 編碼器和解碼器如何圍繞它們執行 UTF-8 步驟,以便重音、CJK 和表情符號往返

名稱 atob 和 btoa 是 20 世紀 90 年代計算的特殊產物。現代命名為 base64Encode 和 base64Decode,API 將接受 Uint8Array 或具有明確編碼宣告的字串。但 atob 和 btoa 仍然存在於瀏覽器中以實現向後相容性。了解它們的含義(以及它們不能做什麼)可以幫助您在編碼 Unicode 文字時避免無提示損壞。

Base64 編碼器和解碼器工具彌補了這一差距:它使用現代程式碼所需的 UTF-8 和 base64 語言。健全的模式是組合的:將文字編碼為 UTF-8 位元組,將位元組轉換為二進位字串合約,然後呼叫 btoa;反轉 atob 周圍的這些步驟。嘗試使用重音、CJK 字元和表情符號,然後要求解碼的文字與每個原始碼點相符。