开发者工具 · Base64 编码器和解码器
atob 和 btoa 代表什么,以及为什么它们只理解拉丁语-1
· 背景
base64 JavaScript 统一码
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 字符和表情符号,然后要求解码的文本与每个原始代码点相匹配。