Base64

Base64 是一组类似的 二进制到文本编码 方案,通过将其转换为基数 64 表示形式,将二进制数据表示为 ASCII 字符串格式。术语 Base64 源自特定的 MIME 内容传输编码

当单独使用“Base64”一词来指代特定的 算法 时,它通常指的是 RFC 4648 第 4 节中概述的 Base64 版本,该版本使用以下字母表来表示基数 64 数字,以及 = 作为填充字符。

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

一个常见的变体是“Base64 URL 安全”,它省略了填充并用 -_ 替换了 +/,以避免在 URL 路径段或查询参数中可能导致问题的字符。如果您没有将数据放在路径段或查询参数中,则不需要此编码——例如,data URL 既没有路径段也没有查询参数,可以使用标准 Base64 编码。

Base64 编码方案通常用于对二进制数据进行编码,以便存储或通过只能处理 ASCII 文本(或仍然无法接受任意二进制数据的 ASCII 的某个超集)的媒体进行传输。这确保了数据在传输过程中保持完整且不会被修改。Base64 的常见应用包括:

  • 通过 MIME 发送电子邮件
  • XML 中存储复杂数据
  • 编码二进制数据,以便将其包含在 data: URL

编码大小增加

每个 Base64 数字代表 6 位数据。因此,输入字符串/二进制文件中的三个 8 位字节(3×8 位 = 24 位)可以用四个 6 位 Base64 数字(4×6 = 24 位)表示。

这意味着字符串或文件的 Base64 版本通常比其源文件大大约三分之一(确切的尺寸增加取决于各种因素,例如字符串的绝对长度、其长度模 3 以及是否使用了填充字符)。

JavaScript 支持

浏览器本身提供了两个 JavaScript 函数用于解码和编码 Base64 字符串。

注意:Base64 是一种二进制编码,而不是文本编码,但 btoaatob 是在 Web 平台支持二进制数据类型之前添加的。因此,这两个函数使用字符串来表示二进制数据,每个字符的 代码点 表示每个字节的值。这导致了一个普遍的误解,即 btoa 可用于编码任意文本数据——例如,创建文本或 HTML 文档的 Base64 data: URL。

但是,字节到代码点的对应关系仅对代码点高达 0x7f 时才可靠地成立。此外,超过 0xff 的代码点会导致 btoa 抛出错误,因为超过了 1 个字节的最大值。下一节详细介绍了在编码任意 Unicode 文本时如何解决此限制。

“Unicode 问题”

由于 btoa 将其输入字符串的代码点解释为字节值,因此如果字符的代码点超过 0xff,则在字符串上调用 btoa 将导致“字符超出范围”异常。对于需要编码任意 Unicode 文本的用例,需要先将字符串转换为其在 UTF-8 中的组成字节,然后对这些字节进行编码。

最简单的解决方案是使用 TextEncoderTextDecoder 在 UTF-8 和字符串的单字节表示之间进行转换。

js
function base64ToBytes(base64) {
  const binString = atob(base64);
  return Uint8Array.from(binString, (m) => m.codePointAt(0));
}

function bytesToBase64(bytes) {
  const binString = Array.from(bytes, (byte) =>
    String.fromCodePoint(byte),
  ).join("");
  return btoa(binString);
}

// Usage
bytesToBase64(new TextEncoder().encode("a Ā 𐀀 文 🦄")); // "YSDEgCDwkICAIOaWhyDwn6aE"
new TextDecoder().decode(base64ToBytes("YSDEgCDwkICAIOaWhyDwn6aE")); // "a Ā 𐀀 文 🦄"

转换任意二进制数据

上一节中的 bytesToBase64base64ToBytes 函数可直接用于在 Base64 字符串和 Uint8Array 之间进行转换。

为了获得更好的性能,可以通过 Web 平台本身的 FileReaderfetch API 本地异步地在 base64 数据 URL 之间进行转换。

js
async function bytesToBase64DataUrl(bytes, type = "application/octet-stream") {
  return await new Promise((resolve, reject) => {
    const reader = Object.assign(new FileReader(), {
      onload: () => resolve(reader.result),
      onerror: () => reject(reader.error),
    });
    reader.readAsDataURL(new File([bytes], "", { type }));
  });
}

async function dataUrlToBytes(dataUrl) {
  const res = await fetch(dataUrl);
  return new Uint8Array(await res.arrayBuffer());
}

// Usage
await bytesToBase64DataUrl(new Uint8Array([0, 1, 2])); // "data:application/octet-stream;base64,AAEC"
await dataUrlToBytes("data:application/octet-stream;base64,AAEC"); // Uint8Array [0, 1, 2]

另请参阅