JavaScript URL 编码完全指南 | encodeURIComponent
直接在浏览器中使用免费的 URL Encode Online,无需安装。
在线试用 URL Encode Online →当你构建搜索 URL、将重定向路径作为查询参数传递,或构建 OAuth 授权请求时,特殊字符(如 &、 = 和空格)会在不进行 URL 编码的情况下悄悄损坏 URL。 JavaScript 提供了三种内置方法—— encodeURIComponent()、 encodeURI() 和 URLSearchParams——每种方法针对不同的使用场景设计, 选择错误的方法是我在代码审查中遇到的大多数编码 bug 的根本原因。 如需在无需编写任何代码的情况下快速进行一次性编码, ToolDeck 的 URL Encoder 可在浏览器中即时处理。本指南深入介绍了所有三种方法(JavaScript ES2015+ / Node.js 10+): 何时使用、它们在空格和保留字符方面的区别、实际的 Fetch 和 API 模式、CLI 用法, 以及导致最隐蔽生产 bug 的四种常见错误。
- ✓encodeURIComponent() 是编码单个参数值的正确选择——它编码除 A–Z、a–z、0–9 和 - _ . ! ~ * ' ( ) 之外的所有字符
- ✓encodeURI() 对完整 URL 进行编码,同时保留结构字符(/ ? # & = :)——切勿将其用于单个值
- ✓URLSearchParams 使用 application/x-www-form-urlencoded 格式自动编码键值对,其中空格变为 + 而非 %20
- ✓双重编码是最常见的生产环境 bug:encodeURIComponent(encodeURIComponent(value)) 会将 %20 变成 %2520
- ✓qs 库原生支持查询字符串中的嵌套对象和数组——标准库 URLSearchParams 不支持此功能
什么是 URL 编码?
URL 编码(正式名称为百分号编码,定义于 RFC 3986)将 URL 中不允许或具有特殊含义的字符转换为安全表示形式。 每个不安全的字节被替换为百分号后跟两个十六进制数字——即该字符的 ASCII 码。 空格变为 %20,&符号变为 %26,正斜杠变为 %2F。
始终安全且永不编码的字符称为非保留字符: 字母 A–Z 和 a–z、数字 0–9,以及四个符号 - _ . ~。其他所有字符在用作数据时都必须编码, 或者在 URL 中具有结构性作用(如 / 分隔路径段, 或 &分隔查询参数)。 实际结果是:查询参数中若包含像"无线键盘 & 鼠标"这样的产品名称, 如果直接传递原始值,将破坏 URL 结构。
https://shop.example.com/search?q=Wireless Keyboard & Mouse&category=peripherals
https://shop.example.com/search?q=Wireless%20Keyboard%20%26%20Mouse&category=peripherals
encodeURIComponent() — 查询参数的正确函数
encodeURIComponent() 是 JavaScript 中 URL 编码的主力函数。 它编码除非保留集(A–Z、a–z、0–9、- _ . ! ~ * ' ( ))之外的所有字符。 关键是,它编码所有在 URL 中具有结构含义的字符——&、 =、?、 #、/—— 这使其可以安全地用于参数值中。无需导入,它是所有 JavaScript 环境中都可用的全局函数。
最小工作示例
// Encode a search query parameter that contains special characters
const searchQuery = 'Wireless Keyboard & Mouse'
const filterStatus = 'in-stock'
const maxPrice = '149.99'
const searchUrl = `https://shop.example.com/products?` +
`q=${encodeURIComponent(searchQuery)}` +
`&status=${encodeURIComponent(filterStatus)}` +
`&maxPrice=${encodeURIComponent(maxPrice)}`
console.log(searchUrl)
// https://shop.example.com/products?q=Wireless%20Keyboard%20%26%20Mouse&status=in-stock&maxPrice=149.99将重定向 URL 编码为查询参数
// The redirect destination is itself a URL — it must be fully encoded
// or the outer URL parser will misinterpret its ? and & as its own
const redirectAfterLogin = 'https://dashboard.internal/reports?view=weekly&team=platform'
const loginUrl = `https://auth.company.com/login?next=${encodeURIComponent(redirectAfterLogin)}`
console.log(loginUrl)
// https://auth.company.com/login?next=https%3A%2F%2Fdashboard.internal%2Freports%3Fview%3Dweekly%26team%3Dplatform
// Decoding on the receiving end
const params = new URLSearchParams(window.location.search)
const next = params.get('next') // Automatically decoded
const nextUrl = new URL(next!) // Safe to parse
console.log(nextUrl.hostname) // dashboard.internal编码非 ASCII 和 Unicode 字符
// encodeURIComponent handles Unicode natively in all modern environments
// Each UTF-8 byte of the character is percent-encoded
const customerName = '张伟 (Beijing)'
const productTitle = '北京 wireless adapter'
const reviewText = '很好 — 非常满意'
console.log(encodeURIComponent(customerName))
// %E5%BC%A0%E4%BC%9F%20(Beijing)
console.log(encodeURIComponent(productTitle))
// %E5%8C%97%E4%BA%AC%20wireless%20adapter
console.log(encodeURIComponent(reviewText))
// %E5%BE%88%E5%A5%BD%20%E2%80%94%20%E9%9D%9E%E5%B8%B8%E6%BB%A1%E6%84%8F
// Decoding back
console.log(decodeURIComponent('%E5%BC%A0%E4%BC%9F%20(Beijing)'))
// 张伟 (Beijing)encodeURIComponent() 使用 JavaScript 引擎内部的 UTF-16 字符串编码, 然后分别编码字符的每个 UTF-8 字节。像 ü(U+00FC)这样的字符编码为 %C3%BC,因为其 UTF-8 表示是两个字节:0xC3 和 0xBC。 这是正确的,符合 URI 标准——服务器应将 UTF-8 字节序列解码回原始码点。JavaScript URL 编码函数——字符参考
三种原生编码方法在编码哪些字符方面有所不同。 下表显示了最常见的问题字符的输出:
| 字符 | URL 中的作用 | encodeURIComponent() | encodeURI() | URLSearchParams |
|---|---|---|---|---|
| Space | 单词分隔符 | %20 | %20 | + |
| & | 参数分隔符 | %26 | kept | %26 |
| = | 键=值 | %3D | kept | %3D |
| + | 编码后的空格(表单) | %2B | %2B | %2B |
| ? | 查询开始 | %3F | kept | %3F |
| # | 片段标识符 | %23 | kept | %23 |
| / | 路径分隔符 | %2F | kept | %2F |
| : | 协议 / 端口 | %3A | kept | %3A |
| @ | 认证分隔符 | %40 | kept | %40 |
| % | 百分号字面量 | %25 | %25 | %25 |
| ~ | 非保留字符 | kept | kept | kept |
关键列是 encodeURIComponent() 与 encodeURI() 对 & 和 = 的处理:encodeURI() 保持它们不变, 这在编码完整 URL 时是正确的,但在编码包含这些字符的值时会造成灾难性后果。
encodeURI() — 何时保留 URL 结构
encodeURI() 设计用于编码完整 URL—— 它保留 URI 结构中有效的所有字符:协议(https://)、 主机、路径分隔符、查询分隔符和片段标识符。 当你收到的 URL 路径段中可能包含空格或非 ASCII 字符,但需要保持其结构完整时,使用它。
清理用户提供的 URL
// A URL pasted from a document with spaces in the path and non-ASCII chars const rawUrl = 'https://cdn.example.com/assets/product images/München chair.png' const safeUrl = encodeURI(rawUrl) console.log(safeUrl) // https://cdn.example.com/assets/product%20images/M%C3%BCnchen%20chair.png // encodeURIComponent would break it — it encodes the :// and all / characters const broken = encodeURIComponent(rawUrl) console.log(broken) // https%3A%2F%2Fcdn.example.com%2Fassets%2Fproduct%20images%2FM%C3%BCnchen%20chair.png // ↑ Not a valid URL — the scheme and slashes are destroyed
URL 构造函数通常比 encodeURI() 更好——它规范化 URL、验证结构, 并提供清晰的 API 来访问每个组件。 将 encodeURI() 保留用于需要字符串结果且已知输入在结构上是有效 URL 的情况。URLSearchParams — 处理查询字符串的现代方法
URLSearchParams 是现代 JavaScript 中构建和解析查询字符串的惯用方式。 它在所有现代浏览器和 Node.js 10+ 中全局可用,并自动处理编码—— 你只需处理普通的键值对,它会生成正确的输出。 一个重要细节:它遵循 application/x-www-form-urlencoded 规范, 将空格编码为 + 而非 %20。这是正确且广泛支持的, 但当你的服务器期望特定格式时应注意这一点。
构建搜索请求 URL
// Building a search API URL with multiple parameters
const filters = {
query: 'standing desk',
category: 'office-furniture',
minPrice: '200',
maxPrice: '800',
inStock: 'true',
sortBy: 'price_asc',
}
const params = new URLSearchParams(filters)
const apiUrl = `https://api.example.com/v2/products?${params}`
console.log(apiUrl)
// https://api.example.com/v2/products?query=standing+desk&category=office-furniture&minPrice=200&maxPrice=800&inStock=true&sortBy=price_asc
// Appending additional params after construction
params.append('page', '2')
params.append('tag', 'ergonomic & adjustable')
console.log(params.toString())
// query=standing+desk&...&tag=ergonomic+%26+adjustable解析传入的查询字符串
// Both browser (window.location.search) and Node.js (req.url) scenarios
function parseWebhookCallbackUrl(rawSearch: string) {
const params = new URLSearchParams(rawSearch)
return {
eventId: params.get('event_id'), // null if missing
timestamp: Number(params.get('ts')),
signature: params.get('sig'),
redirectUrl: params.get('redirect'), // Automatically decoded
tags: params.getAll('tag'), // Handles repeated keys
}
}
const callbackQuery = '?event_id=evt_9c2f&ts=1717200000&sig=sha256%3Dabc123&redirect=https%3A%2F%2Fdashboard.internal%2F&tag=payment&tag=webhook'
const parsed = parseWebhookCallbackUrl(callbackQuery)
console.log(parsed.redirectUrl) // https://dashboard.internal/
console.log(parsed.tags) // ['payment', 'webhook']如何在 JavaScript Fetch 请求中对参数进行 URL 编码
在生产代码中,URL 编码最常出现在 fetch() 调用中——无论是构建请求 URL 还是在请求体中发送表单数据。 每种场景都有其正确的处理方式。
GET 请求——编码查询参数
async function searchInventory(params: {
sku?: string
warehouse: string
minStock: number
updatedAfter?: Date
}): Promise<{ items: unknown[]; total: number }> {
const searchParams = new URLSearchParams()
if (params.sku) searchParams.set('sku', params.sku)
searchParams.set('warehouse', params.warehouse)
searchParams.set('min_stock', String(params.minStock))
if (params.updatedAfter) searchParams.set('updated_after', params.updatedAfter.toISOString())
const url = `https://inventory.internal/api/items?${searchParams}`
const res = await fetch(url, {
headers: {
'Authorization': `Bearer ${process.env.INVENTORY_API_KEY}`,
'Accept': 'application/json',
},
})
if (!res.ok) {
throw new Error(`Inventory API ${res.status}: ${await res.text()}`)
}
return res.json()
}
const results = await searchInventory({
warehouse: 'eu-west-1',
minStock: 10,
updatedAfter: new Date('2025-01-01'),
})
console.log(`Found ${results.total} items`)POST 请求——URL 编码表单体
// application/x-www-form-urlencoded POST — used by OAuth token endpoints,
// legacy form-submission APIs, and some webhook providers
async function requestOAuthToken(
clientId: string,
clientSecret: string,
code: string,
redirectUri: string,
): Promise<{ access_token: string; expires_in: number }> {
const body = new URLSearchParams({
grant_type: 'authorization_code',
client_id: clientId,
client_secret: clientSecret,
code,
redirect_uri: redirectUri, // URLSearchParams encodes this automatically
})
const res = await fetch('https://oauth.provider.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString(),
// body: body — also works directly in modern environments (Fetch spec accepts URLSearchParams)
})
if (!res.ok) {
const err = await res.json()
throw new Error(`OAuth error: ${err.error_description ?? err.error}`)
}
return res.json()
}当你需要在不设置脚本的情况下测试或调试编码后的 URL 时, 将原始值直接粘贴到 URL Encoder 中——它即时编码和解码,向你展示浏览器和服务器将看到的确切内容。 适用于检查 OAuth 重定向 URI、webhook 回调 URL 和 CDN 签名请求参数。
使用 Node.js 和 Shell 进行命令行 URL 编码
对于 shell 脚本、CI 流水线或快速调试,以下几种命令行方法无需编写完整脚本即可使用。
# ── Node.js one-liners — cross-platform (macOS, Linux, Windows) ────────
# encodeURIComponent — encode a single value
node -e "console.log(encodeURIComponent(process.argv[1]))" "Wireless Keyboard & Mouse"
# Wireless%20Keyboard%20%26%20Mouse
# Build a complete query string
node -e "console.log(new URLSearchParams(JSON.parse(process.argv[1])).toString())" '{"q":"standing desk","category":"office-furniture","page":"1"}'
# q=standing+desk&category=office-furniture&page=1
# Decode a percent-encoded string
node -e "console.log(decodeURIComponent(process.argv[1]))" "Wireless%20Keyboard%20%26%20Mouse"
# Wireless Keyboard & Mouse
# ── curl — automatic encoding ───────────────────────────────────────────
# curl --data-urlencode encodes values automatically in GET and POST
curl -G "https://api.example.com/search" --data-urlencode "q=Wireless Keyboard & Mouse" --data-urlencode "category=office furniture"
# ── Python one-liner (available on most systems) ─────────────────────────
python3 -c "from urllib.parse import quote; print(quote(input(), safe=''))"
# (type the string and press Enter)
# ── jq + bash: encode every value in a JSON object ───────────────────────
echo '{"q":"hello world","tag":"node & express"}' | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); console.log(new URLSearchParams(d).toString())"
# q=hello+world&tag=node+%26+express高性能替代方案:qs
内置的 URLSearchParams 不支持嵌套对象或数组—— 而许多 API 和框架使用的查询字符串形式就是如此。 qs 库(每周 npm 下载量超过 3000 万次) 处理各种实际使用中的查询字符串模式:带括号表示法的嵌套对象 (filters[status]=active)、重复键、 自定义编码器和可配置的数组序列化格式。
npm install qs # or pnpm add qs
import qs from 'qs'
// URLSearchParams cannot represent this structure natively
const reportFilters = {
dateRange: {
from: '2025-01-01',
to: '2025-03-31',
},
status: ['published', 'archived'],
author: { id: 'usr_4f2a9c1b', role: 'editor' },
workspace: 'ws-platform-eu',
}
// qs produces bracket-notation query strings used by Express, Rails, Django REST
const query = qs.stringify(reportFilters, { encode: true })
console.log(query)
// dateRange%5Bfrom%5D=2025-01-01&dateRange%5Bto%5D=2025-03-31&status%5B0%5D=published&status%5B1%5D=archived&author%5Bid%5D=usr_4f2a9c1b&author%5Brole%5D=editor&workspace=ws-platform-eu
// Human-readable (no encoding) — useful for debugging
console.log(qs.stringify(reportFilters, { encode: false }))
// dateRange[from]=2025-01-01&dateRange[to]=2025-03-31&status[0]=published&status[1]=archived...
// Parsing back
const parsed = qs.parse(query)
console.log(parsed.dateRange) // { from: '2025-01-01', to: '2025-03-31' }
console.log(parsed.status) // ['published', 'archived']对于扁平的键值参数,URLSearchParams 始终是正确的选择—— 它是内置的,零开销,且被普遍支持。 仅当需要嵌套结构、重复键以外的数组序列化格式,或与期望括号表示法查询字符串的后端框架集成时, 才使用 qs。
常见错误
我在生产代码库中反复看到这四种错误——大多数是静默失败, 只有在值包含特殊字符时才会出现,这正是那种能通过单元测试但只在真实用户数据中才暴露的 bug。
错误 1——对查询参数值使用 encodeURI()
问题: encodeURI() 不编码 &、 = 或 +。 当值包含这些字符时,它们被解释为查询字符串语法,静默地拆分或覆盖参数。 修复: 对单个值始终使用 encodeURIComponent()。
// ❌ encodeURI does not encode & and = in the value
// "plan=pro&promo=SAVE20" is treated as two separate params
const planName = 'Pro Plan (save=20% & free trial)'
const url = `/checkout?plan=${encodeURI(planName)}`
// /checkout?plan=Pro%20Plan%20(save=20%%20&%20free%20trial)
// ↑ & breaks the query string// ✅ encodeURIComponent encodes & and = safely
const planName = 'Pro Plan (save=20% & free trial)'
const url = `/checkout?plan=${encodeURIComponent(planName)}`
// /checkout?plan=Pro%20Plan%20(save%3D20%25%20%26%20free%20trial)
// ↑ = and & are both encoded错误 2——对已编码字符串进行双重编码
问题: 对已经进行百分号编码的值调用 encodeURIComponent(), 会将 % 变为 %25, 使 %20 变成 %2520。 服务器解码一次后收到的是字面量 %20 而非空格。 修复: 先解码再重新编码,或确保值不被编码两次。
// ❌ encodedParam already contains %20 — encoding again produces %2520
const encodedParam = 'Berlin%20Office'
const url = `/api/locations/${encodeURIComponent(encodedParam)}`
// /api/locations/Berlin%2520Office
// Server sees: "Berlin%20Office" (a literal percent-twenty, not a space)// ✅ Decode first if the value may already be encoded
const maybeEncoded = 'Berlin%20Office'
const clean = decodeURIComponent(maybeEncoded) // 'Berlin Office'
const url = `/api/locations/${encodeURIComponent(clean)}`
// /api/locations/Berlin%20Office — correct错误 3——未考虑 URLSearchParams 和 encodeURIComponent 之间 + / %20 的差异
问题: URLSearchParams 将空格编码为 +(application/x-www-form-urlencoded), 而 encodeURIComponent() 使用 %20。 在同一个 URL 中混用两者——例如将预编码字符串追加到 URLSearchParams 输出中—— 会产生不一致的编码,令部分解析器困惑。 修复: 选择一种方法并在整个 URL 构建函数中一致使用。
// ❌ Mixed: URLSearchParams uses + for spaces, but we're appending
// a manually-encoded segment that uses %20
const base = new URLSearchParams({ category: 'office furniture' })
const extra = `sort=${encodeURIComponent('price asc')}`
const url = `/api/products?${base}&${extra}`
// /api/products?category=office+furniture&sort=price%20asc
// ↑ two different space encodings in the same URL// ✅ Use URLSearchParams exclusively — consistent encoding throughout
const params = new URLSearchParams({
category: 'office furniture',
sort: 'price asc',
})
const url = `/api/products?${params}`
// /api/products?category=office+furniture&sort=price+asc错误 4——忘记对包含斜杠的路径段进行编码
问题: 用作 REST 路径段的资源标识符(如文件路径 reports/2025/q1.pdf) 包含服务器路由器将其解释为路径分隔符的 / 字符, 导致路由到不存在的端点。 修复: 对可能包含斜杠的路径段始终使用 encodeURIComponent()。
// ❌ The file path contains / — the server receives a 3-segment path
// instead of a single resource ID
const filePath = 'reports/2025/q1-financials.pdf'
const url = `https://storage.example.com/objects/${filePath}`
// https://storage.example.com/objects/reports/2025/q1-financials.pdf
// → Server routes to /objects/:year/:filename — 404 or wrong resource// ✅ encodeURIComponent encodes / as %2F — single path segment
const filePath = 'reports/2025/q1-financials.pdf'
const url = `https://storage.example.com/objects/${encodeURIComponent(filePath)}`
// https://storage.example.com/objects/reports%2F2025%2Fq1-financials.pdf
// → Server receives the full file path as one resource identifierJavaScript URL 编码方法——快速对比
| 方法 | 空格编码为 | 编码 & = ? | 编码 # / : | 使用场景 | 需要安装 |
|---|---|---|---|---|---|
| encodeURIComponent() | %20 | ✅ yes | ✅ yes | 编码单个参数值 | No |
| encodeURI() | %20 | ❌ no | ❌ no | 清理完整 URL 字符串 | No |
| URLSearchParams | + | ✅ yes | ✅ yes | 构建和解析查询字符串 | No |
| URL constructor | 按组件自动处理 | auto | auto | 构建和规范化完整 URL | No |
| qs library | %20(可配置) | ✅ yes | ✅ yes | 查询字符串中的嵌套对象和数组 | npm install qs |
在绝大多数情况下,选择归结为三种场景。当从结构化数据构建多参数查询字符串时, 使用 URLSearchParams——这是最安全、可读性最强的选项。 在模板字符串 URL 中编码单个值、用于路径段,或用于期望 %20 而非 + 表示空格的系统(如 AWS S3 签名 URL)时, 使用 encodeURIComponent()。 仅当查询字符串携带 URLSearchParams 无法原生表示的嵌套对象或数组时,才使用 qs。
常见问题
相关工具
如需无需编写任何代码即可一键编码或解码, 请将字符串直接粘贴到 URL Encoder 中——它在浏览器中即时处理百分号编码和解码, 编码后的输出可直接复制到 fetch 调用或终端中。
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.