Base64

Base64 是一组相似的二进制到文本(binary-to-text)的编码规则,让二进制数据在解释成 64 进制的表现形式后能够用 ASCII 字符串的格式表示出来。Base64 这个词出自一种特定的 MIME 内容传输编码

当单独使用术语"Base64"指代特定算法时,通常指的是 RFC 4648 第 4 节中概述的 Base64 版本。该版本使用以下字母表来表示基于 64 进制的数字,以及 = 作为填充字符:

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

一种常见的变体是“URL 安全的 Base64”,它省略了填充,并将 +/ 替换为 -_,以避免在 URL 路径段或查询参数中可能引起问题的字符的问题。

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

编码后大小增加

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

这意味着字符串或文件的 Base64 版本通常比其原来的内容大大约三分之一(确切的大小增加取决于各种因素,如字符串的绝对长度、它除以 3 的长度余数,以及是否使用填充字符)。

JavaScript 支持

浏览器原生提供了两个 JavaScript 函数,用于解码和编码 Base64 字符串:

  • btoa():从二进制数据字符串创建一个 Base64 编码的 ASCII 字符串(“btoa”应看作“从二进制到 ASCII”)
  • atob():解码通过 Base64 编码的字符串数据(“atob”应看作“从 ASCII 到二进制”)

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

然而,字节到码位的对应关系只能可靠地适用于最高为 0x7f 的码位。此外,超过 0xff 的码位将导致 btoa 抛出错误,因为超过了 1 字节的最大值。下一节详细介绍了如何解决在编码任意 Unicode 文本时的这种限制。

Unicode 问题

由于 btoa 将其输入字符串的码位解释为字节值,因此如果字符的码位超过 0xff,调用 btoa 将导致“Character Out Of Range”异常。对于需要编码任意 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);
}

// 用法
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());
}

// 用法
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]

参见