上传文件
  1. base64hub
  2. 学习 Base64
  3. Uint8Array.toBase64()
指南

JavaScript 中的 Uint8Array.toBase64 与 fromBase64

JavaScript 终于有了直接处理字节的 Base64 API,它支持 Base64URL,还能选择解码的严格程度。大多数用到 btoa、atob 和手写循环的地方都可以换成它。

一行代码完成编码和解码

这个 API 新增了三个 Base64 方法和三个 hex 方法:

方法作用
bytes.toBase64(options)Uint8Array 转 Base64 字符串
Uint8Array.fromBase64(string, options)Base64 字符串转新的 Uint8Array
bytes.setFromBase64(string, options)解码到已有的 Uint8Array 中
bytes.toHex()Uint8Array 转小写 hex 字符串
Uint8Array.fromHex(string)hex 字符串转新的 Uint8Array
bytes.setFromHex(string)把 hex 解码到已有的 Uint8Array 中

文本必须先变成字节。TextEncoder 总是输出 UTF-8,所以处理 Unicode 不需要任何技巧:

const bytes = new TextEncoder().encode("Grüße, 世界");
const b64 = bytes.toBase64();
console.log(b64); // "R3LDvMOfZSwg5LiW55WM"

const back = new TextDecoder().decode(Uint8Array.fromBase64(b64));
console.log(back); // "Grüße, 世界"

ArrayBuffer(例如来自 fetch().arrayBuffer() 或 crypto.subtle.digest())需要先套一个视图:new Uint8Array(buffer).toBase64()。其他类型化数组根本没有这些方法。要编码 Uint16Array 的原始内存,请这样包装:new Uint8Array(u16.buffer, u16.byteOffset, u16.byteLength)。

选项:alphabet、omitPadding、lastChunkHandling

alphabet

"base64"(默认值)使用 + 和 /,"base64url" 使用 - 和 _。这个选项对编码和解码都有效,设为某一种字母表的解码器会拒绝另一种字母表的字符。

const data = new Uint8Array([251, 255]);
data.toBase64();                                         // "+/8="
data.toBase64({ alphabet: "base64url" });                // "-_8="
data.toBase64({ alphabet: "base64url", omitPadding: true }); // "-_8"

Uint8Array.fromBase64("-_8", { alphabet: "base64url" }); // Uint8Array [251, 255]
Uint8Array.fromBase64("-_8");                            // SyntaxError

omitPadding

toBase64 默认会添加 = 填充,即使使用 Base64URL 字母表也是如此。要得到 JWT 风格的输出,请传入 omitPadding: true。解码时的填充由 lastChunkHandling 控制。

lastChunkHandling

这个选项决定 fromBase64 和 setFromBase64 如何处理最后一组字符。下面用解码结果为 Hi?> 字节的输入测试三种模式:

输入"loose"(默认)"strict""stop-before-partial"
SGk/Pg==Hi?>Hi?>Hi?>
SGk/Pg(无填充)Hi?>SyntaxErrorHi?(在 Pg 之前停止)
SGk/Ph==(填充比特非零)Hi?>SyntaxErrorHi?>
SGk/P(剩下一个字符)SyntaxErrorSyntaxErrorHi?
  • loose 的行为与 atob 相同:填充可有可无,最后一个字符中多余的比特会被忽略。
  • strict 只接受 RFC 4648 所描述的规范、带填充的输入。当编码形式必须唯一时使用它,例如需要比较或签名 Base64 字符串的场景。
  • stop-before-partial 会解码所有完整的 4 字符分组,不处理不完整的尾部。它用于流式处理,即分组的剩余部分会在下一个数据块中到达。

在所有模式下,ASCII 空白(空格、制表符、换行符)都会被跳过,所以 MIME 折行的输入可以直接解码。字母表以外的其他任何字符都会抛出 SyntaxError。

写入已有的缓冲区

setFromBase64 和 setFromHex 会解码到你已经准备好的 Uint8Array 中,并返回用掉了多少:

const target = new Uint8Array(8);
const result = target.setFromBase64("SGVsbG8gd29ybGQ=");
console.log(result); // { read: 8, written: 6 }

Hello world 有 11 个字节,而目标只能容纳 8 个。解码器只写入放得下的完整 3 字节分组,所以它在 6 个字节后停止,并报告读取了 8 个字符。用字符串的剩余部分和目标的 subarray 再次调用即可继续。配合 stop-before-partial,就能把数据流逐块解码到预先分配好的缓冲区中:

const out = new Uint8Array(1024);
const { read, written } = out.setFromBase64("SGk/Pg", {
  lastChunkHandling: "stop-before-partial",
});
// read: 4, written: 3. Keep "Pg" and prepend it to the next chunk.

hex 方法遵循同样的模式。toHex() 总是输出小写,fromHex() 接受大小写两种形式,数字个数为奇数时会抛出 SyntaxError:

new Uint8Array([0xde, 0xad, 0xbe, 0xef]).toHex(); // "deadbeef"
Uint8Array.fromHex("CAFE");                       // Uint8Array [202, 254]

为什么 btoa 和 atob 不好用

btoa 和 atob 诞生于浏览器的早期,处理的是“二进制字符串”:每个字符编码都是 0 到 255 之间字节值的 JavaScript 字符串。这带来了三个问题。

Unicode 要么报错,要么悄悄出错。U+00FF 以上的字符会让 btoa 抛出 InvalidCharacterError(Chrome:“The string to be encoded contains characters outside of the Latin1 range”)。U+0080 到 U+00FF 之间的字符更糟:它们会以 Latin-1 编码,不报任何错误。btoa("Grüße") 返回 R3L832U=,而其他所有系统期望的 UTF-8 编码是 R3LDvMOfZQ==。

解码返回的是字符串,而不是字节。atob("Q2Fmw6k=") 返回 "Café"。要得到真正的文本,得先把字符串转成字节,再按 UTF-8 解码,这需要写一个循环,或者 Uint8Array.from(atob(s), c => c.charCodeAt(0))。

处理大输入需要小心。常见的一行写法 btoa(String.fromCharCode(...bytes)) 会把每个字节都作为函数参数传入,遇到 1 MB 的数组就会抛出 RangeError: Maximum call stack size exceeded。你必须分块构建字符串。

此外,它们不支持 Base64URL,也无法控制填充或严格程度。老办法 btoa(unescape(encodeURIComponent(text))) 确实能得到 UTF-8 的 Base64,但依赖已被弃用的 unescape。有了新 API,这一切都变成了 new TextEncoder().encode(text).toBase64()。

浏览器与 Node.js 支持情况

这些方法来自 TC39 提案“Uint8Array to/from Base64”,该提案于 2025 年 7 月进入 Stage 4,属于 ECMAScript 2026 的一部分。

运行时首个支持全部六个方法的版本
Chrome、Edge140
Firefox133
Safari(macOS 和 iOS)18.2
Node.js25.0.0

随着 Chrome 140 发布,该特性于 2025 年 9 月成为 Baseline,所有当前的主流浏览器都已支持。在 Node.js 中,我们直接测试了这些方法。Node 25.0.0 和 26.12.1 拥有全部六个方法。LTS 版本 Node 24.22.1 默认一个都没有。它的 V8 接受实验性的 --js-base-64 参数,但在我们的测试中,这个参数只开放了 toBase64、fromBase64、toHex 和 fromHex,没有 setFromBase64 和 setFromHex,所以应当视 Node 24 为不支持。在 Node 24 及更早版本上,请使用 Buffer.from(b64, "base64") 和 buf.toString("base64"),它们也接受 "base64url"。根据 MDN 的兼容性数据,Deno 2.5 和 Bun 1.1.22 同样支持这个 API。

兼容旧运行时的写法

先做特性检测,不支持时回退到分块处理的 btoa/atob。这个版本已在 Node 24(回退路径)和 Node 26(原生路径)上用 UTF-8 文本和 1 MB 数组测试过:

function bytesToBase64(bytes) {
  if (typeof bytes.toBase64 === "function") return bytes.toBase64();
  let bin = "";
  for (let i = 0; i < bytes.length; i += 0x8000) {
    bin += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
  }
  return btoa(bin);
}

function base64ToBytes(b64) {
  if (typeof Uint8Array.fromBase64 === "function") return Uint8Array.fromBase64(b64);
  return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
}

这个回退只覆盖默认选项。如果你需要在所有环境中使用 base64url、strict 或 setFromBase64,请使用完整的 polyfill:core-js 提供了 es.uint8-array.to-base64、es.uint8-array.from-base64 及相关模块。

不想写代码也能试试各种编码:使用 Base64 编码器和解码器,URL 安全字母表则用 Base64URL。更多 JavaScript 示例(包括 Node 的 Buffer)见 JavaScript 中的 Base64 和 Node.js 中的 Base64。

常见问题

这个新的 Base64 API 现在可以在生产环境中使用吗?

在浏览器中可以:Chrome 和 Edge 140、Firefox 133、Safari 18.2 都已支持。Node.js 需要 25 或更高版本。如果要支持更老的浏览器或 Node 24 及以下版本,请保留兼容写法。

Uint8Array.fromBase64 支持 Base64URL 吗?

支持,传入 { alphabet: "base64url" } 即可。不加这个选项时,- 和 _ 会导致 SyntaxError。在默认的宽松模式下,两种字母表的填充都是可选的。

怎样用 toBase64 编码字符串?

先把它转成 UTF-8 字节:new TextEncoder().encode(text).toBase64()。解码时反过来:new TextDecoder().decode(Uint8Array.fromBase64(b64))。

为什么我的 Buffer 或 Uint16Array 上没有 toBase64 方法?

这些方法只存在于 Uint8Array 及其子类上。Node 的 Buffer 从 Node 25 起才有。对于其他类型化数组,请包装底层内存:new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength)。

继续阅读