site logo

Marico's space

JavaScript 中的 Base64 编码:实用指南与真实示例

前端技术 2026-07-07 14:49:14 12

最近折腾 JavaScript 里的 Base64 编码,踩了几个坑,特别是中文字符串直接用 btoa() 直接报错的体验实在太酸爽。这篇把 Base64 的常见坑和实用模式说清楚。

Base64 在 Web 开发里无处不在——JWT 令牌、图片 Data URL、API 载荷、HTTP Basic Auth 头都靠它。但看似简单的 API 其实有不少弯弯绕绕。

Base64 实际上做了什么

Base64 用 64 个可打印字符(A-Z、a-z、0-9、+、/)把二进制数据转成 ASCII 文本。每 3 字节输入变成 4 字符输出,编码结果比原始数据大约大 33%。

这种编码存在是因为很多协议(邮件、HTTP、JSON)设计之初只考虑文本而非二进制。Base64 填补了这个空白。

btoa() 的陷阱

原生的 btoa() 函数处理 ASCII 完全没问题:

btoa("Hello World"); // "SGVsbG8gV29ybGQ="
atob("SGVsbG8gV29ybGQ="); // "Hello World"

但只要编码任何包含非 Latin1 字符的内容试试:

btoa("你好世界");
// Uncaught DOMException: Failed to execute 'btoa' on 'Window':
// The string to be encoded contains characters outside of the Latin1 range.

处理国际化内容时这是必踩的坑。解决方案取决于你的 JavaScript 运行环境。

编码 UTF-8 字符串(浏览器端)

方法一:encodeURIComponent 技巧

经典方案——先转义 Unicode 字符,再编码:

function encodeBase64(str) { return btoa(encodeURIComponent(str).replace( /%([0-9A-F]{2})/g, (_, p1) => String.fromCharCode('0x' + p1) ));
} function decodeBase64(b64) { return decodeURIComponent( atob(b64).split('').map( c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) ).join('') );
} encodeBase64("你好世界"); // "5L2g5aW977yM5LiW55WM"
decodeBase64("5L2g5aW977yM5LiW55WM"); // "你好世界"

方法二:TextEncoder(现代方案)

更干净、更易读:

function encodeBase64Unicode(str) { const bytes = new TextEncoder().encode(str); let binary = ''; bytes.forEach(b => binary += String.fromCharCode(b)); return btoa(binary);
} function decodeBase64Unicode(b64) { const binary = atob(b64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return new TextDecoder().decode(bytes);
}

两种方法都可行。我更倾向 TextEncoder,可读性更好。但 encodeURIComponent 方案在老旧浏览器里兼容性更广。

图片转 Base64(Data URL)

把小图片内嵌成 Base64 Data URL 可以减少 HTTP 请求:

function fileToDataUrl(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(file); });
} // Usage: preview an uploaded image
input.addEventListener('change', async (e) => { const dataUrl = await fileToDataUrl(e.target.files[0]); previewImg.src = dataUrl; // data:image/jpeg;base64,/9j/4AAQ...
});

适用场景:4KB 以下的小图标、一次性图形、需要完全自包含的 HTML 文件。

不适用场景:大图片。一张 2MB 的照片转成字符串后约 2.6MB,会阻塞渲染并撑爆内存。

Node.js 里的 Base64

Node.js 原生支持 UTF-8,用 Buffer 处理即可:

// Encode
const encoded = Buffer.from('你好世界', 'utf-8').toString('base64');
// "5L2g5aW977yM5LiW55WM" // Decode
const decoded = Buffer.from(encoded, 'base64').toString('utf-8');
// "你好世界"

比浏览器端简单太多了。如果在服务端做 Base64 相关操作,Node.js 很省心。

Base64URL:JWT 变种

JWT 令牌用 Base64URL 而不是标准 Base64。区别如下:

标准 Base64 Base64URL
+ -
/ _
= 填充 无填充
// Convert standard Base64 to Base64URL
function toBase64Url(b64) { return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
} // Convert back
function fromBase64Url(b64url) { let b64 = b64url.replace(/-/g, '+').replace(/_/g, '/'); while (b64.length % 4) b64 += '='; return b64;
}

如果你在解析或生成 JWT,这个转换搞错了会导致静默认证失败,调试起来非常头秃。

常见坑

1. Base64 不是加密

这点值得反复强调:Base64 是编码,不是加密。任何人都能解码它。永远不要用它来"保护"密码、API 密钥或其他敏感数据。这类场景请用真正的加密算法(AES、RSA 等)。

2. 大文件的内存问题

一个 10MB 的视频文件转成 Base64 后约 13MB。在浏览器里,这个字符串作为 JavaScript 字符串存储(UTF-16),实际内存占用可能翻倍到 26MB。多个大文件这样处理会直接崩溃标签页。

解决方案:大文件用 BlobURL.createObjectURL() 处理,别用 Base64。

3. 阻塞主线程

同步编码大文件会冻住整个 UI。把处理逻辑搬到 Web Worker 里:

// worker.js
self.onmessage = (e) => { const reader = new FileReaderSync(); const result = reader.readAsDataURL(e.data.file); self.postMessage(result);
}; // main.js
const worker = new Worker('worker.js');
worker.postMessage({ file: largeFile });
worker.onmessage = (e) => { console.log('Done:', e.data.substring(0, 50) + '...');
};

快速参考

// Browser: ASCII only
btoa("Hello"); // "SGVsbG8="
atob("SGVsbG8="); // "Hello" // Browser: UTF-8 (use TextEncoder)
const bytes = new TextEncoder().encode("你好");
btoa(String.fromCharCode(...bytes)); // "5L2g5aW9" // Node.js: UTF-8 native
Buffer.from('你好', 'utf-8').toString('base64'); // "5L2g5aW9" // Base64URL (for JWT)
btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); // File to Data URL
new FileReader().readAsDataURL(file); // HTTP Basic Auth header
'Basic ' + btoa('user:pass');

快速测试 Base64

调试的时候经常需要快速编解码 Base64 字符串而不想写代码。找个能正确处理 UTF-8 的在线 Base64 编码解码工具会方便很多,同时显示标准版和 URL 安全版——这在对比 JWT 载荷或调试 API 响应时特别有用。

核心要点:Base64 概念简单,但坑不少(UTF-8、URL 安全变种、内存占用),花时间理解底层机制比只会复制粘贴 btoa() 强得多。