JavaScript Base64 解码完整指南 — atob() 与 Buffer
直接在浏览器中使用免费的 Base64解码器,无需安装。
在线试用 Base64解码器 →每当我调试生产环境中的认证问题,第一时间拿起的就是 Base64 解码工具 — JWT 载荷、Webhook 签名和编码的配置值都隐藏在 Base64 字符串中。 JavaScript 提供了两种主要的内置解码方式: atob()(浏览器 + Node.js 16+)和 Buffer.from(encoded, 'base64').toString() (Node.js)— 当原始数据包含 Unicode 字符时,两者的行为差异很大。 如需无需编写代码的快速解码, ToolDeck's Base64 Decoder 可在浏览器中即时完成。本指南涵盖两种环境 — 面向 Node.js 16+ 和现代浏览器(Chrome 80+、Firefox 75+、Safari 14+)— 包含生产级示例:UTF-8 恢复、URL 安全变体、JWT 解码、文件、API 响应、Node.js 流, 以及在真实代码库中反复导致乱码输出的四个常见错误。
- ✓atob(encoded) 是浏览器原生函数,在 Node.js 16+ 中也可全局使用,但返回的是二进制字符串 — 对于任何超出 ASCII 范围的内容,需使用 TextDecoder 恢复 UTF-8 文本。
- ✓Buffer.from(encoded, "base64").toString("utf8") 是 Node.js 的惯用做法,无需额外步骤即可自动处理 UTF-8。
- ✓URL 安全 Base64(JWT 中使用)将 + 替换为 -、/ 替换为 _,并去除 = 填充。调用 atob() 前需先还原这些字符,或在 Node.js 18+ 中使用 Buffer.from(encoded, "base64url").toString()。
- ✓解码前需去除空白字符和换行符 — GitHub Contents API 和许多 MIME 编码器会在每行 60–76 个字符处对 Base64 输出进行换行。
- ✓Uint8Array.prototype.fromBase64()(TC39 Stage 3)已在 Node.js 22+ 和 Chrome 130+ 中可用,最终将统一两种环境的处理方式。
什么是 Base64 解码?
Base64 解码是编码的逆过程 — 它将 64 个字符的 ASCII 表示转换回原始二进制数据或文本。 每 4 个 Base64 字符恰好映射回 3 个字节。编码字符串末尾的 = 填充字符告知解码器最后一个 3 字节组追加了多少额外字节。
Base64 不是加密 — 任何持有编码字符串的人都可以完全逆向此操作。 其目的是传输安全:为 7 位 ASCII 文本设计的协议和存储格式无法处理任意二进制字节, Base64 弥补了这一差距。JavaScript 常见的解码场景包括:检查 JWT 载荷、 解包来自环境变量的 Base64 编码 JSON 配置、从 REST API 提取二进制文件内容, 以及在浏览器中解码 data URI。
ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=
deploy-bot:sk-prod-a7f2c91e4b3d8
atob() — 浏览器原生解码函数
atob()(ASCII-to-binary)自 IE10 起就已在浏览器中可用, 并作为 WinterCG 兼容性计划的一部分,在 Node.js 16.0 中成为全局函数。 它在 Deno、Bun 和 Cloudflare Workers 中也原生可用,无需任何导入。
该函数返回一个二进制字符串:JavaScript 字符串中每个字符的码位等于一个原始字节值(0–255)。 这一点很重要:如果原始数据是包含 U+007F 以上字符(重音字母、西里尔文、CJK、emoji)的 UTF-8 文本, 返回的字符串是原始字节序列,而非可读文本。使用 TextDecoder 来恢复(下一节介绍)。
最小可运行示例
// Decoding an HTTP Basic Auth credential pair received in a request header
// Authorization: Basic ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=
function parseBasicAuth(header: string): { serviceId: string; apiKey: string } {
const base64Part = header.replace(/^Basics+/i, '')
const decoded = atob(base64Part)
const [serviceId, apiKey] = decoded.split(':')
return { serviceId, apiKey }
}
const auth = parseBasicAuth('Basic ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=')
console.log(auth.serviceId) // deploy-bot
console.log(auth.apiKey) // sk-prod-a7f2c91e4b3d8往返验证
// Verify lossless recovery for ASCII-only content const original = 'service:payments region:eu-west-1 env:production' const encoded = btoa(original) const decoded = atob(encoded) console.log(encoded) // c2VydmljZTpwYXltZW50cyByZWdpb246ZXUtd2VzdC0xIGVudjpwcm9kdWN0aW9u console.log(decoded === original) // true
atob() 和 btoa() 是 WinterCG Minimum Common API 的一部分 — 该规范同样管理非浏览器运行时中的 Fetch、URL 和 crypto。 它们在 Node.js 16+、Bun、Deno 和 Cloudflare Workers 中行为完全一致。解码后恢复 UTF-8 文本
atob() 最常见的陷阱是误解其返回类型。 当原始文本在 Base64 编码前已被编码为 UTF-8 时,atob() 返回的是 Latin-1 二进制字符串,而非可读文本:
// '张伟' was UTF-8 encoded then Base64 encoded before transmission const encoded = '5byg5oiV' // ❌ atob() returns the raw UTF-8 bytes as a Latin-1 string — garbled output console.log(atob(encoded)) // "å¼ ä¼Ÿ" ← byte values misread as Latin-1
正确的做法是使用 TextDecoder 将这些原始字节解释为 UTF-8:
TextDecoder 方案 — 适用于任意 Unicode 输出
// Unicode-safe Base64 decode utilities
function fromBase64(encoded: string): string {
const binary = atob(encoded)
const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0))
return new TextDecoder().decode(bytes)
}
function toBase64(text: string): string {
const bytes = new TextEncoder().encode(text)
const chars = Array.from(bytes, byte => String.fromCharCode(byte))
return btoa(chars.join(''))
}
// Works with any language or script
const orderNote = '已确认:张伟 — 上海仓库,数量:250'
const encoded = toBase64(orderNote)
const decoded = fromBase64(encoded)
console.log(decoded === orderNote) // true
console.log(decoded)
// 已确认:张伟 — 上海仓库,数量:250Buffer.from(encoded, 'base64').toString('utf8')。 它会自动将解码后的字节解释为 UTF-8,对于大输入速度更快。Node.js 中的 Buffer.from() — 完整解码指南
在 Node.js 中,Buffer 是处理所有二进制操作(包括 Base64 解码)的惯用 API。 它原生处理 UTF-8,返回真正的 Buffer(二进制安全), 并自 Node.js 18 起支持 URL 安全变体的 'base64url' 编码快捷方式。
解码环境变量中的配置
// Server config stored as Base64 in an env variable (avoids JSON escaping in shell)
// DB_CONFIG=eyJob3N0IjoiZGItcHJpbWFyeS5pbnRlcm5hbCIsInBvcnQiOjU0MzIsImRhdGFiYXNlIjoiYW5hbHl0aWNzX3Byb2QiLCJtYXhDb25uZWN0aW9ucyI6MTAwfQ==
const raw = Buffer.from(process.env.DB_CONFIG!, 'base64').toString('utf8')
const dbConfig = JSON.parse(raw)
console.log(dbConfig.host) // db-primary.internal
console.log(dbConfig.port) // 5432
console.log(dbConfig.maxConnections) // 100从 .b64 文件还原二进制文件
import { readFileSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
// Read the Base64-encoded certificate and restore the original binary
const encoded = readFileSync(join(process.cwd(), 'dist', 'cert.b64'), 'utf8').trim()
const certBuf = Buffer.from(encoded, 'base64')
writeFileSync('./ssl/server.crt', certBuf)
console.log(`Restored ${certBuf.length} bytes`)
// Restored 2142 bytes带错误处理的异步解码
import { readFile, writeFile } from 'node:fs/promises'
async function decodeBase64File(
encodedPath: string,
outputPath: string,
): Promise<number> {
try {
const encoded = await readFile(encodedPath, 'utf8')
const binary = Buffer.from(encoded.trim(), 'base64')
await writeFile(outputPath, binary)
return binary.length
} catch (err) {
const code = (err as NodeJS.ErrnoException).code
if (code === 'ENOENT') throw new Error(`File not found: ${encodedPath}`)
if (code === 'EACCES') throw new Error(`Permission denied: ${encodedPath}`)
throw err
}
}
// Restore a PDF stored as Base64
const bytes = await decodeBase64File('./uploads/invoice.b64', './out/invoice.pdf')
console.log(`Decoded ${bytes} bytes — PDF restored`)Base64 解码函数 — 参数参考
以下是两种主要原生解码 API 的参数快速参考,供编写或审查代码时查阅使用。
atob(encodedData)
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| encodedData | string | 是 | 使用 +、/、= 字符的标准 Base64 字符串。URL 安全变体(-、_)会抛出 InvalidCharacterError。不允许有空白字符。 |
Buffer.from(input, inputEncoding) / .toString(outputEncoding)
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| input | string | Buffer | TypedArray | ArrayBuffer | 必填 | 要解码的 Base64 编码字符串,或包含编码字节的 Buffer。 |
| inputEncoding | BufferEncoding | "utf8" | 标准 Base64(RFC 4648 §4)设为 "base64",URL 安全 Base64(RFC 4648 §5,Node.js 18+)设为 "base64url"。 |
| outputEncoding | string | "utf8" | .toString() 输出的编码。可读文本使用 "utf8",与 atob() 输出兼容的 Latin-1 二进制字符串使用 "binary"。 |
| start | integer | 0 | 解码后 Buffer 中开始读取的字节偏移量。作为第二个参数传给 .toString()。 |
| end | integer | buf.length | 停止读取的字节偏移量(不含)。作为第三个参数传给 .toString()。 |
URL 安全 Base64 — 解码 JWT 和 URL 参数
JWT 对所有三个片段使用 URL 安全 Base64(RFC 4648 §5)。URL 安全 Base64 将 + 替换为 -, / 替换为 _,并去除末尾的 = 填充。 不还原这些字符直接传给 atob() 会产生错误输出或抛出异常。
浏览器 — 解码前还原字符和填充
function decodeBase64Url(input: string): string {
const base64 = input.replace(/-/g, '+').replace(/_/g, '/')
const padded = base64 + '==='.slice(0, (4 - base64.length % 4) % 4)
const binary = atob(padded)
const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0))
return new TextDecoder().decode(bytes)
}
// Inspect a JWT payload segment (the middle part between the two dots)
const jwtToken = 'eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsIndvcmtzcGFjZUlkIjoid3NfM2E3ZjkxYzIiLCJleHAiOjE3MTcyMDM2MDB9'
const payload = decodeBase64Url(jwtToken)
const claims = JSON.parse(payload)
console.log(claims.userId) // usr_9f2a1c3e8b4d
console.log(claims.role) // editor
console.log(claims.workspaceId) // ws_3a7f91c2Node.js 18+ — 原生 'base64url' 编码
// Node.js 18 added 'base64url' as a first-class Buffer encoding — no manual replace needed
function decodeJwtSegment(segment: string): Record<string, unknown> {
const json = Buffer.from(segment, 'base64url').toString('utf8')
return JSON.parse(json)
}
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsIndvcmtzcGFjZUlkIjoid3NfM2E3ZjkxYzIiLCJleHAiOjE3MTcyMDM2MDB9.SIGNATURE'
const [headerB64, payloadB64] = token.split('.')
const header = decodeJwtSegment(headerB64)
const payload = decodeJwtSegment(payloadB64)
console.log(header.alg) // HS256
console.log(payload.role) // editor
console.log(payload.workspaceId) // ws_3a7f91c2解码来自文件和 API 响应的 Base64
在生产代码中,Base64 解码最常发生在消费以编码形式传递内容的外部 API 时。 两种场景在空白字符处理以及二进制与文本输出方面都有重要的注意事项。 如果只是在调试时检查编码的响应,直接将其粘贴到 Base64 Decoder 中即可 — 它能即时处理标准和 URL 安全模式。
解码来自 GitHub Contents API 的内容
// GitHub Contents API returns file content as Base64, wrapped at 60 chars per line
async function fetchDecodedFile(
owner: string,
repo: string,
path: string,
token: string,
): Promise<string> {
const res = await fetch(
`https://api.github.com/repos/${owner}/${repo}/contents/${path}`,
{ headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github.v3+json' } }
)
if (!res.ok) throw new Error(`GitHub API ${res.status}: ${res.statusText}`)
const data = await res.json() as { content: string; encoding: string }
if (data.encoding !== 'base64') throw new Error(`Unexpected encoding: ${data.encoding}`)
// ⚠️ GitHub wraps at 60 chars — strip newlines before decoding
const clean = data.content.replace(/\n/g, '')
return Buffer.from(clean, 'base64').toString('utf8')
}
const openApiSpec = await fetchDecodedFile('acme-corp', 'platform-api', 'openapi.json', process.env.GITHUB_TOKEN!)
const spec = JSON.parse(openApiSpec)
console.log(`API version: ${spec.info.version}`)解码来自 API 的 Base64 编码二进制数据(浏览器)
// Some APIs return binary content (images, PDFs) as Base64 JSON fields
async function downloadDecodedFile(endpoint: string, authToken: string): Promise<void> {
const res = await fetch(endpoint, { headers: { Authorization: `Bearer ${authToken}` } })
if (!res.ok) throw new Error(`Download failed: ${res.status}`)
const { filename, content, mimeType } = await res.json() as {
filename: string; content: string; mimeType: string
}
// Decode Base64 → binary bytes → Blob
const binary = atob(content)
const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0))
const blob = new Blob([bytes], { type: mimeType })
// Trigger browser download
const url = URL.createObjectURL(blob)
const a = Object.assign(document.createElement('a'), { href: url, download: filename })
a.click()
URL.revokeObjectURL(url)
}
await downloadDecodedFile('/api/reports/latest', sessionStorage.getItem('auth_token')!)在 Node.js 和 Shell 中进行命令行 Base64 解码
对于 CI/CD 脚本、调试会话或一次性解码任务,Shell 工具和 Node.js 单行命令比完整脚本更快。 请注意,macOS 和 Linux 的标志名称不同。
# ── macOS / Linux system base64 ───────────────────────────────────────
# Standard decoding (macOS uses -D, Linux uses -d)
echo "ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=" | base64 -d # Linux
echo "ZGVwbG95LWJvdDpzay1wcm9kLWE3ZjJjOTFlNGIzZDg=" | base64 -D # macOS
# Decode a .b64 file to its original binary
base64 -d ./dist/cert.b64 > ./ssl/server.crt # Linux
base64 -D -i ./dist/cert.b64 -o ./ssl/server.crt # macOS
# URL-safe Base64 — restore + and / before decoding
echo "eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ" | tr '-_' '+/' | base64 -d
# ── Node.js one-liner — works on Windows too ───────────────────────────
node -e "process.stdout.write(Buffer.from(process.argv[1], 'base64').toString())" "ZGVwbG95LWJvdA=="
# deploy-bot
# URL-safe (Node.js 18+)
node -e "process.stdout.write(Buffer.from(process.argv[1], 'base64url').toString())" "eyJhbGciOiJIUzI1NiJ9"
# {"alg":"HS256"}base64 使用 -D(大写)进行解码,而 Linux 使用 -d(小写)。 这会导致 CI 脚本静默失败 — 当目标平台不能保证是 Linux 时,请使用 Node.js 单行命令。高性能替代方案:js-base64
选择库的主要原因是跨环境一致性。如果你发布的包需要在浏览器和 Node.js 中均无需打包器配置即可运行, 使用 Buffer 需要环境检测, 使用 atob() 需要 TextDecoder 变通方案。js-base64(npm 周下载量超 1 亿)可透明地处理两种情况。
npm install js-base64 # or pnpm add js-base64
import { fromBase64, fromBase64Url, isValid } from 'js-base64'
// Standard decoding — Unicode-safe, works in browser and Node.js
const raw = fromBase64('eyJldmVudElkIjoiZXZ0XzdjM2E5ZjFiMmQiLCJ0eXBlIjoiY2hlY2tvdXRfY29tcGxldGVkIiwiY3VycmVuY3kiOiJFVVIiLCJhbW91bnQiOjE0OTAwfQ==')
const event = JSON.parse(raw)
console.log(event.type) // checkout_completed
console.log(event.currency) // EUR
// URL-safe decoding — no manual character replacement needed
const jwtPayload = fromBase64Url('eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciJ9')
const claims = JSON.parse(jwtPayload)
console.log(claims.role) // editor
// Validate before decoding untrusted input
const untrusted = 'not!valid@base64#'
if (!isValid(untrusted)) {
console.error('Rejected: invalid Base64 input')
}
// Binary output — second argument true returns Uint8Array
const pngBytes = fromBase64('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', true)
console.log(pngBytes instanceof Uint8Array) // true带语法高亮的终端输出
编写 CLI 调试工具或检查脚本时,对于大型 JSON 载荷,纯 console.log 输出难以阅读。chalk(最下载的终端着色 npm 包)结合 Base64 解码, 可产生清晰、易于扫描的终端输出 — 适用于 JWT 检查、API 响应调试和配置审计。
npm install chalk # chalk v5+ is ESM-only — use import, not require
import chalk from 'chalk'
// Decode and display any Base64 value with smart type detection
function inspectBase64(encoded: string, label = 'Decoded value'): void {
let decoded: string
try {
decoded = Buffer.from(encoded.trim(), 'base64').toString('utf8')
} catch {
console.error(chalk.red('✗ Invalid Base64 input'))
return
}
console.log(chalk.bold.cyan(`\n── ${label} ──`))
// Attempt JSON pretty-print
try {
const parsed = JSON.parse(decoded)
console.log(chalk.green('Type:'), chalk.yellow('JSON'))
for (const [key, value] of Object.entries(parsed)) {
const display = typeof value === 'object' ? JSON.stringify(value) : String(value)
console.log(chalk.green(` ${key}:`), chalk.white(display))
}
return
} catch { /* not JSON */ }
// Plain text fallback
console.log(chalk.green('Type:'), chalk.yellow('text'))
console.log(chalk.white(decoded))
}
// Inspect a Base64-encoded JWT payload
const tokenParts = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2U4YjRkIiwicm9sZSI6ImVkaXRvciIsImV4cCI6MTcxNzIwMzYwMH0.SIGNATURE'.split('.')
inspectBase64(tokenParts[0], 'JWT Header')
inspectBase64(tokenParts[1], 'JWT Payload')
// ── JWT Header ──
// Type: JSON
// alg: HS256
// typ: JWT
//
// ── JWT Payload ──
// Type: JSON
// userId: usr_9f2a1c3e8b4d
// role: editor
// exp: 1717203600使用 Node.js 流解码大型 Base64 文件
当 Base64 编码的文件超过约 50 MB 时,使用 readFileSync() 将其完全加载到内存会造成问题。 Node.js 流允许分块解码数据 — 但 Base64 要求每个块为 4 个字符的倍数(每个 4 字符组恰好解码为 3 个字节), 以避免在块边界处出现填充错误。
import { createReadStream, createWriteStream } from 'node:fs'
import { pipeline } from 'node:stream/promises'
async function streamDecodeBase64(inputPath: string, outputPath: string): Promise<void> {
const readStream = createReadStream(inputPath, { encoding: 'utf8', highWaterMark: 4 * 1024 * 192 })
const writeStream = createWriteStream(outputPath)
let buffer = ''
await pipeline(
readStream,
async function* (source) {
for await (const chunk of source) {
buffer += (chunk as string).replace(/\s/g, '') // strip any whitespace/newlines
// Decode only complete 4-char groups to avoid mid-stream padding issues
const remainder = buffer.length % 4
const safe = buffer.slice(0, buffer.length - remainder)
buffer = buffer.slice(buffer.length - remainder)
if (safe.length > 0) yield Buffer.from(safe, 'base64')
}
if (buffer.length > 0) yield Buffer.from(buffer, 'base64')
},
writeStream,
)
}
// Decode a 200 MB video that was stored as Base64
await streamDecodeBase64('./uploads/product-demo.b64', './dist/product-demo.mp4')
console.log('Stream decode complete')4 × 1024 × 192 = 786,432 个字符(768 KB)。 对于 50 MB 以下的文件, readFile() + Buffer.from(content.trim(), 'base64') 更简单且足够快。常见错误
以下四个错误在 JavaScript 代码库中反复出现 — 往往要等到非 ASCII 字符或行换行的 API 响应 进入生产环境的解码路径时才被发现。
错误 1 — 对 UTF-8 内容使用 atob() 而不搭配 TextDecoder
问题: atob() 返回的二进制字符串中每个字符代表一个原始字节值。 UTF-8 多字节序列(西里尔文、CJK、重音字符)会显示为乱码 Latin-1 字符。 解决方案: 将输出包装在 TextDecoder 中。
// ❌ atob() returns the raw UTF-8 bytes as a Latin-1 string const encoded = '0JDQu9C10LrRgdC10Lkg0JjQstCw0L3QvtCy' const decoded = atob(encoded) console.log(decoded) // "Алексей Р˜РІР°РЅРѕРІ" ← wrong
// ✅ Use TextDecoder to correctly interpret the UTF-8 bytes const encoded = '0JDQu9C10LrRgdC10Lkg0JjQstCw0L3QvtCy' const binary = atob(encoded) const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0)) const decoded = new TextDecoder().decode(bytes) console.log(decoded) // Алексей Иванов ✓
错误 2 — 将 URL 安全 Base64 直接传给 atob()
问题: JWT 片段使用 - 和 _ 代替 + 和 /,且没有填充。atob() 可能返回错误数据或抛出异常。 解决方案: 先还原标准字符并重新添加填充。
// ❌ URL-safe JWT segment passed directly — unreliable const jwtPayload = 'eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ' const decoded = atob(jwtPayload) // May produce wrong result or throw
// ✅ Restore standard Base64 chars and padding first
function decodeBase64Url(input: string): string {
const b64 = input.replace(/-/g, '+').replace(/_/g, '/')
const pad = b64 + '==='.slice(0, (4 - b64.length % 4) % 4)
const bin = atob(pad)
const bytes = Uint8Array.from(bin, ch => ch.charCodeAt(0))
return new TextDecoder().decode(bytes)
}
const decoded = decodeBase64Url('eyJ1c2VySWQiOiJ1c3JfOWYyYTFjM2UifQ')
// {"userId":"usr_9f2a1c3e"} ✓错误 3 — 未去除行换行的 Base64 中的换行符
问题: GitHub Contents API 和 MIME 编码器会在每行 60–76 个字符处对 Base64 输出进行换行。atob() 遇到 \n 字符时会抛出 InvalidCharacterError。 解决方案: 解码前去除所有空白字符。
// ❌ GitHub API content field contains embedded newlines const data = await res.json() const decoded = atob(data.content) // ❌ throws InvalidCharacterError
// ✅ Strip newlines (and any other whitespace) before decoding const data = await res.json() const clean = data.content.replace(/\s/g, '') const decoded = atob(clean) // ✓
错误 4 — 对解码后的二进制内容调用 .toString()
问题: 当原始数据为二进制(图片、PDF、音频)时,调用 .toString('utf8') 会将无法识别的字节序列替换为 U+FFFD, 静默损坏输出。 解决方案: 将结果保持为 Buffer — 不要转换为字符串。
// ❌ .toString('utf8') corrupts binary content
import { readFileSync, writeFileSync } from 'node:fs'
const encoded = readFileSync('./uploads/invoice.b64', 'utf8').trim()
const corrupted = Buffer.from(encoded, 'base64').toString('utf8') // ❌
writeFileSync('./out/invoice.pdf', corrupted) // ❌ unreadable PDF// ✅ Keep the Buffer as binary — do not convert to a string
import { readFileSync, writeFileSync } from 'node:fs'
const encoded = readFileSync('./uploads/invoice.b64', 'utf8').trim()
const binary = Buffer.from(encoded, 'base64') // ✓ raw bytes preserved
writeFileSync('./out/invoice.pdf', binary) // ✓ valid PDFJavaScript Base64 解码方法 — 快速对比
| 方法 | UTF-8 输出 | 二进制输出 | URL 安全 | 可用环境 | 需要安装 |
|---|---|---|---|---|---|
| atob() | ❌ 需要 TextDecoder | ✅ 二进制字符串 | ❌ 手动还原 | Browser, Node 16+, Bun, Deno | 否 |
| TextDecoder + atob() | ✅ UTF-8 | ✅ 通过 Uint8Array | ❌ 手动还原 | Browser, Node 16+, Deno | 否 |
| Buffer.from().toString() | ✅ utf8 | ✅ 保持为 Buffer | ✅ base64url (Node 18+) | Node.js, Bun | 否 |
| Uint8Array.fromBase64() (TC39) | ✅ 通过 TextDecoder | ✅ 原生 | ✅ alphabet 选项 | Chrome 130+, Node 22+ | 否 |
| js-base64 | ✅ 始终 | ✅ Uint8Array | ✅ 内置 | 通用 | npm install |
仅当解码内容可确保为 ASCII 文本时才使用 atob()。对于浏览器中的用户输入或多语言文本, 使用 TextDecoder + atob()。对于 Node.js 服务端代码,Buffer 是正确的默认选择 — 它自动处理 UTF-8 并保持二进制数据完整。对于跨环境库, js-base64 可消除所有边界情况。
常见问题
相关工具
无需编写任何代码,一键解码,将你的 Base64 字符串直接粘贴到 Base64 Decoder 中 — 它在浏览器中即时处理标准和 URL 安全模式。
Alex is a front-end and Node.js developer with extensive experience building web applications and developer tooling. He is passionate about web standards, browser APIs, and the JavaScript ecosystem. In his spare time he contributes to open-source projects and writes about modern JavaScript patterns, performance optimisation, and everything related to the web platform.
Sophie is a full-stack developer focused on TypeScript across the entire stack — from React frontends to Express and Fastify backends. She has a particular interest in type-safe API design, runtime validation, and the patterns that make large JavaScript codebases stay manageable. She writes about TypeScript idioms, Node.js internals, and the ever-evolving JavaScript module ecosystem.