配了多年二进制转换,今天才发现 btoa 那个 UTF-8 绕路从来不是必须的——今天 Uint8Array 把这件事彻底原生化了
每个处理二进制数据的项目里,都藏着一个长得差不多的工具函数:
// 把字节变字符串,绕路 Latin-1
function bytesToBase64(bytes) {
return btoa(String.fromCharCode(...bytes));
}
// 把字符串变字节,用 charCodeAt 逐字节拆
function base64ToBytes(b64) {
return Uint8Array.from(atob(b64), c => c.charCodeAt(0));
}
这两个函数没有明显错误——它们确实能用。但它们存在的原因是 btoa 和 atob 从第一天设计就不是用来处理字节的,它们操作的是 Latin-1 字符,不是 Uint8Array。
这层错位今天被彻底封掉了。
三行代码,告别所有绕路
const bytes = new Uint8Array([72, 101, 108, 108, 111]); // "Hello"
// 双向 Base64
const b64 = bytes.toBase64(); // 'SGVsbG8='
const restored = Uint8Array.fromBase64(b64); // Uint8Array [72, 101, 108, 108, 111]
// 双向 Hex
const hex = bytes.toHex(); // '48656c6c6f'
const copy = Uint8Array.fromHex(hex);
toBase64() / fromBase64() / toHex() / fromHex(),四个方法,零绕路。
旧方案的三个坑
坑一:参数列表上限
String.fromCharCode(…bytes) 把每个字节当参数传入,大文件直接爆掉:
const largeFile = crypto.getRandomValues(new Uint8Array(1_000_000));
// RangeError: Maximum call stack size exceeded
原生方法没有这个天花板——内部是直接内存操作,不走 JavaScript 函数调用栈。
坑二:UTF-8 字符会静默损坏
btoa 只认 Latin-1 字符,遇到中文或 emoji 直接抛异常:
// 以前要这样绕路
const text = '你好';
const bytes = new TextEncoder().encode(text); // UTF-8 字节
const b64 = btoa(String.fromCharCode(...bytes)); // 隐式 Latin-1 解释,可能乱码
// 现在直接
const b64 = new TextEncoder().encode('你好').toBase64(); // '5L2g5aW9'
坑三:toHex 写轮询
// 以前
const hex = Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
// 现在
const hex = bytes.toHex();
结合 Web Crypto:哈希输出一行搞定
async function sha256Hex(text) {
const digest = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(text)
);
return new Uint8Array(digest).toHex();
}
await sha256Hex('hello');
// '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
以前输出一个哈希要四步:取 digest → 转成 Uint8Array → 逐字节 toString(16) → 拼 join。现在两行。
URL-safe Base64:一个选项,不用字符串替换
标准 Base64 用 + 和 /,在 URL 和文件名里会转义。传统解法是一串 .replace():
const token = b64.replace(/+/g, '-').replace(///g, '_').replace(/=/g, '');
// 现在
const token = bytes.toBase64({ alphabet: 'base64url', omitPadding: true });
// 一个选项,全搞定
解码同样接受 { alphabet: ‘base64url’ } 选项,对称闭合。
浏览器支持
Chrome 140、Firefox 133、Safari 18.2 起,Node.js 25、Deno 2.5、Bun 1.1.22 同步支持。Baseline 2025 已广泛可用,生产环境可直接使用。
降级方案:
if (typeof Uint8Array.prototype.toBase64 !== 'function') {
// 保留原有 btoa/atob 方案
}
配了多年二进制转换,每次写完 String.fromCharCode(…bytes) 都要在注释里写一行”勿动”。今天 UTF-8 字节和字符串之间的那层 Latin-1 伪装,终于可以删了。
评论区
0 条评论
登录后可评论。