JavaScript 里的 Base64:btoa 与中文乱码

btoa 只接受 Latin-1 字符,中文码点超过 255 就直接抛 InvalidCharacterError。正确做法是先用 TextEncoder 编成 UTF-8 字节,再逐字节转成字符串喂给 btoa;解码反过来做。

b64file — converter · local

粘贴即自动处理,不用点按钮(Ctrl+Enter 立刻转换 / Esc 清空)

粘贴、拖拽、Ctrl+V 截图都行

[01]

最基础的两个 API

btoa('hello')         // "aGVsbG8="
atob('aGVsbG8=')      // "hello"

这两行只在内容是纯 ASCII 时成立。一旦出现中文、emoji、日文,下面这个问题就来了。

[02]

中文为什么一定报错

btoa 要求输入里每个字符都落在 0–255,中文码点远大于这个范围,于是直接抛 InvalidCharacterError。正确写法是先转字节再编码:

function b64encode(str) {
  const bytes = new TextEncoder().encode(str)  // 先编成 UTF-8 字节
  let bin = ''
  for (const b of bytes) bin += String.fromCharCode(b)
  return btoa(bin)
}

解码反过来:atob 拿到字节串,再用 TextDecoder 还原成文本。不要写 String.fromCharCode(...bytes),大文件一次展开会爆栈,上面用循环就是为了避开它。

[03]

Node 里写法不一样

Buffer.from(str).toString('base64')        // 编码
Buffer.from(b64, 'base64').toString('utf8') // 解码
Buffer.from(b64, 'base64url')               // 处理 -_ 与缺失的填充

Node 直接吃 Buffer,不需要 TextEncoder 那一套;反过来把这段抄进浏览器,会报 Buffer is not defined。

[04]

base64url:前端最常踩的变体

JWT 和 URL 参数里的 Base64 会把 + 换成 -、/ 换成 _,并去掉结尾的 =。直接丢给 atob 会解不出来或出来一串乱码。

处理只需两步:把 - _ 换回 + /,再按 4 的倍数补上 =。

常见问题

btoa 和 Base64 是一回事吗?

btoa 是浏览器提供的一个函数,做的正是 Base64 编码;它只处理 Latin-1,所以中文要自己先转字节。

大文件怎么处理?

别一次性读成字符串。用 FileReader.readAsDataURL 或分块读再拼接;几十 MB 以上交给后端或命令行更合适。

解出来是乱码?

两个常见原因:拿到的是 base64url(要换字符补填充),或者原始内容本来就不是 UTF-8。

相关工具与指南