中文Base64实测:btoa为什么报错,é为什么更容易踩坑
调试接口或整理配置时,经常需要把一段文本转换成 Base64。英文示例很顺利,换成中文却抛异常;更隐蔽的是,某些非 ASCII 字符虽然不报错,结果仍然与 UTF-8 服务端不一致。本文用一组可复现的小例子区分这两类问题。
1. 先复现两种不同的结果
btoa('hello'); // aGVsbG8=
btoa('中文'); // InvalidCharacterError
btoa('é'); // 6Q==
上面的第三行最容易误判:没有异常,并不代表它按 UTF-8 编码了。若约定采用 UTF-8,é 对应的 Base64 应是 w6k=。直接调用 btoa 得到的 6Q==,编码的是单字节 0xE9;UTF-8 中这个字符的字节是 0xC3、0xA9。
MDN 对 btoa 的输入约束是“二进制字符串”,每个字符代表一个字节。因此,需要先明确文本到字节的编码,再进行 Base64 转换。
2. 一份适合小文本的实现
下面实现没有引入依赖,也没有使用旧式的 escape/unescape 转换。编码与解码明确采用 UTF-8。
function encodeUtf8Base64(text) {
const bytes = new TextEncoder().encode(text);
let binary = '';
for (const byte of bytes) {
binary += String.fromCharCode(byte);
}
return btoa(binary);
}
function decodeUtf8Base64(base64) {
const binary = atob(base64);
const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0));
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
}
这里没有把整个数组展开传给 String.fromCharCode,避免在输入增大时受到函数参数数量限制。它仍是面向小文本的同步实现;大文件不宜直接套用到页面主线程。
解码时设置 fatal: true,是为了让非法 UTF-8 字节明确失败。例如 /w== 的 Base64 语法可以解出字节 0xFF,但该字节不是完整的合法 UTF-8 文本。工具应提示错误,而不是悄悄显示替换字符,再让使用者误以为数据正确。
3. 实际执行的结果
本次在 Node.js v16.18.1 中运行了同一组 Web API 函数,并用 assert 做往返与异常检查:
| 原始文本 | 编码结果 | 解码检查 |
|---|---|---|
| hello | aGVsbG8= | 还原成功 |
| 中文 | 5Lit5paH | 还原成功 |
| 😀 | 8J+YgA== | 还原成功 |
| A、换行、B | QQpC | 保留换行 |
| 空字符串 | 空字符串 | 还原成功 |
另外验证了:直接 btoa('中文') 抛错;解码 %%% 抛错;以严格 UTF-8 解码 /w== 抛错。é 的直接编码 6Q== 与 UTF-8 编码 w6k= 也验证为不同。
读者可以再加入自己的断言:
const original = '中文😀\nhello';
const encoded = encodeUtf8Base64(original);
console.assert(decodeUtf8Base64(encoded) === original);
console.assert(encodeUtf8Base64('é') === 'w6k=');
4. 接入页面时的三个边界
1. 文本编码不要先 trim。前后空格和换行可能本来就是数据;Base64 输入是否容忍空白,应单独说明。
2. 标准 Base64 与 Base64URL 不是完全相同的输入格式。上面的函数不负责把 -、_ 自动转换成标准字母表,也不处理 data: URL 前缀。
3. Base64 是编码,不是加密。可逆转换不能承担保密功能;日志与截图也不要包含真实口令或令牌。
把“文本使用什么编码”和“字节怎样表示成 Base64”分成两个明确步骤,既能解释中文异常,也能发现 é 这种不报错但结果不一致的情况。
参考文档:
- MDN Web Docs:《Window: btoa() method》
- MDN Web Docs:《TextDecoder: fatal property》
- 点赞
- 收藏
- 关注作者
评论(0)