前端知库

Uint8Array.fromBase64/toBase64 和 btoa/atob 有什么区别?中文怎样转 Base64?

btoa/atob 处理的是“每个字符代表一个字节”的二进制字符串,遇到中文这种码点超过 255 的字符会抛 InvalidCharacterError。中文要先用 TextEncoder 转成 UTF-8 字节再编码。Uint8Array.toBase64 和 fromBase64 直接在字节数组和 Base64 之间转换,还支持 base64url 字母表。

参考答案

简答

Base64 编码的对象是字节,不是文字。btoa 接收的是一种“二进制字符串”,每个字符当成一个字节,所以每个字符的码点必须小于 256。直接 btoa('中文') 会抛出 InvalidCharacterError。

中文要分两步:先用 new TextEncoder().encode(text) 把文字转成 UTF-8 字节,再把字节编码成 Base64。解码时反过来,先得到字节,再用 TextDecoder 还原成文字。

Uint8Array.prototype.toBase64() 和 Uint8Array.fromBase64() 就是为这件事准备的,它们直接在 Uint8Array 和 Base64 字符串之间转换,不用先拼一个二进制字符串。它们还能选 base64url 字母表、省略末尾的 =,解码时会忽略空白字符。

要注意兼容性。这两个方法 2025 年 9 月起才在主流浏览器的最新版本里都能用(Baseline 2025),老环境需要 polyfill 或者继续用 TextEncoder 加 btoa 的写法。

为什么 btoa 处理不了中文

先看 btoa 遇到中文时的表现,以及 MDN 推荐的绕法:

JavaScript
console.log(btoa('abc')); // YWJj
try {
  btoa('中文');
} catch (error) {
  console.log(error.name); // InvalidCharacterError
}
const bytes = new TextEncoder().encode('中文');
console.log(bytes.length); // 6
const binary = Array.from(bytes, (byte) => String.fromCodePoint(byte)).join('');
const encoded = btoa(binary);
console.log(encoded); // 5Lit5paH
const back = Uint8Array.from(atob(encoded), (char) => char.codePointAt(0));
console.log(new TextDecoder().decode(back)); // 中文

abc 每个字符的码点都小于 256,btoa 可以直接处理。可“中”的码点远大于 255,一个字符放不进一个字节,btoa 不知道该怎么拆,只能抛错。

所以先用 TextEncoder 把文字按 UTF-8 编成字节。两个汉字在 UTF-8 里各占 3 个字节,一共 6 个字节。再把每个字节变成一个码点 0 到 255 的字符,拼成二进制字符串交给 btoa,就能得到 5Lit5paH。

解码时 atob 返回的也是二进制字符串,要先把每个字符的码点取出来变回字节,再用 TextDecoder 按 UTF-8 解成文字。中间拼字符串、拆字符串的两步,就是 btoa/atob 用起来别扭的地方。

Uint8Array 的新方法省掉了哪一步

有了 toBase64 和 fromBase64,字节数组可以直接和 Base64 互转:

JavaScript
const encoded = new TextEncoder().encode('中文').toBase64(); // '5Lit5paH'
const text = new TextDecoder().decode(Uint8Array.fromBase64(encoded)); // '中文'

和上面的写法相比,中间不再需要“二进制字符串”这个过渡形态。MDN 也建议,手上已经是 Uint8Array 时用 toBase64() 代替 btoa();解码的数据如果本来就是字节而不是 ASCII 文本,用 fromBase64() 代替 atob()。

这两个方法还多了几个选项。alphabet: 'base64url' 用 - 和 _ 代替 + 和 /,编码结果可以直接放进 URL。toBase64({ omitPadding: true }) 去掉末尾的 =。fromBase64 会忽略字符串里的 ASCII 空白,遇到不属于字母表的字符抛 SyntaxError。它的 lastChunkHandling 选项控制最后一组不满 4 个字符时怎么办:默认 loose 允许不带 =,strict 要求必须补齐 =。

兼容性怎么处理

MDN 把 toBase64 和 fromBase64 标为 Baseline 2025,2025 年 9 月起在各主流浏览器最新版都可用。具体版本是 Chrome 140、Firefox 133、Safari 18.2。用 Node.js 22.23.3 实测,typeof Uint8Array.fromBase64 是 'undefined',这个版本还没有提供。

所以需要支持旧环境时,可以用 core-js 或 es-arraybuffer-base64 提供的 polyfill,或者继续用前面 TextEncoder 加 btoa 的写法。

常见追问

Base64 能用来加密吗?

不能。Base64 只是把字节换一种字符表示,任何人都能解回去,没有密钥,也不提供保密性。

图片转 Base64 有什么代价?

Base64 后的长度大约是原始数据的 4/3。图片预览更推荐 object URL,见《File、Blob 和 object URL 怎样用于文件预览?》。

TextEncoder 编出来的字节为什么和字符串 length 对不上?

length 数的是 UTF-16 码元,UTF-8 字节数是另一回事,一个汉字是 1 个码元、3 个字节。字符计数的问题见《为什么字符串 length 不一定等于用户看到的字符数?》。