什么是 URL 编码
URL(统一资源定位符)最初被设计为仅由 ASCII 字符组成的字符串。然而真实世界的 URL 经常需要包含中文、Emoji、空格以及各种特殊符号。为了让这些非 ASCII 字符能够安全地在网络中传输,浏览器与服务器约定了一套**百分号编码(Percent-Encoding)**机制:将每个需要转义的字节表示为 % 后跟两个十六进制数字。
例如空格会被编码为 %20,中文字符「工具」在 UTF-8 下占 6 个字节,会被编码为 %E5%B7%A5%E5%85%B7。
这套机制的依据是 RFC 3986,它规定了哪些字符是保留字符(在 URL 中有特殊含义,不能直接使用),哪些是未保留字符(安全可直接使用)。
保留字符与未保留字符
| 分类 | 字符集合 | 说明 |
|---|---|---|
| 未保留字符 | A-Z a-z 0-9 - _ . ~ | 无需编码,可直接出现在 URL 任意位置 |
| 保留字符(通用) | : / ? # [ ] @ | 作为 URL 结构分隔符,有特殊含义 |
| 保留字符(组件) | ! $ & ' ( ) * + , ; = | 在特定组件(如查询串)中作为分隔符 |
关键认知:一个字符是否需要编码,取决于它出现在 URL 的哪个部分以及是否需要保留其特殊语义。这正是
encodeURI与encodeURIComponent两个函数的根本分界线。
encodeURI 与 encodeURIComponent 的本质区别
JavaScript 提供了两个内置的 URL 编码函数,它们的差异不在于「编码能力」,而在于保留哪些字符不编码。
| 函数 | 不编码的保留字符 | 适用场景 |
|---|---|---|
encodeURI | ;/?:@&=+$,# | 编码完整的 URL,保留 URL 结构分隔符 |
encodeURIComponent | 仅 - _ . ~ ! * ' ( ) | 编码 URL 的单个组成部分(如查询参数值) |
const url = 'https://example.com/search?q=工具盒子&lang=zh';
// encodeURI 保留 ? & = 等结构字符,适合编码完整 URL
encodeURI(url);
// → 'https://example.com/search?q=%E5%B7%A5%E5%85%B7%E7%9B%92%E5%AD%90&lang=zh'
// encodeURIComponent 会编码 ? & =,会破坏 URL 结构,仅用于编码参数值
encodeURIComponent(url);
// → 'https%3A%2F%2Fexample.com%2Fsearch%3Fq%3D%E5%B7%A5...'
// 正确用法:分别编码参数值,再拼接成完整 URL
const q = encodeURIComponent('工具盒子');
const full = `https://example.com/search?q=${q}&lang=zh`;
// → 'https://example.com/search?q=%E5%B7%A5%E5%85%B7%E7%9B%92%E5%AD%90&lang=zh'
一句话记忆法
encodeURI编码的是「整条 URL」,所以保留 URL 的骨架字符(/ : ? & = #等)。encodeURIComponent编码的是「URL 的一块零件」,所以把这些骨架字符也当作普通文本编码掉,避免它们在新上下文中被误读为分隔符。
中文与多字节字符的编码过程
URL 编码以字节为单位,而非字符。对于 ASCII 字符(1 字节),直接转为一个 %XX;对于中文、Emoji 等多字节字符,先按 UTF-8 拆成多个字节,再逐个编码。
以中文字符「具」为例:
字符「具」
↓ UTF-8 编码
字节序列: E5 85 B7(3 个字节)
↓ 百分号编码
结果: %E5%85%B7
// 用 TextEncoder 观察字符的字节构成
const bytes = new TextEncoder().encode('具');
console.log(bytes); // Uint8Array(3) [229, 133, 183]
// 229 = 0xE5, 133 = 0x85, 183 = 0xB7
console.log(encodeURIComponent('具')); // %E5%85%B7
Emoji 通常占 4 个字节(UTF-8),因此一个 Emoji 会变成 4 个 %XX:
encodeURIComponent('🚀'); // %F0%9F%9A%80
这也解释了为什么不同语言、不同编码(如 GBK)下同一中文的 URL 编码结果不同。现代 Web 标准统一采用 UTF-8,浏览器与服务器默认按 UTF-8 解码,因此始终用 UTF-8 是最稳妥的选择。
解码的陷阱:+ 与 %20
URL 中空格的表示存在两种约定,这是最容易踩的坑:
| 约定来源 | 空格表示 | 常见场景 |
|---|---|---|
| RFC 3986 | %20 | URL 路径、现代查询串 |
application/x-www-form-urlencoded | + | HTML 表单提交(form 默认编码) |
// encodeURI 与 encodeURIComponent 都把空格编码为 %20
encodeURIComponent('a b'); // 'a%20b'
// 但 decodeURIComponent 不会把 + 还原为空格
decodeURIComponent('a+b'); // 'a+b'(仍是加号)
// 表单场景下,+ 表示空格,需先替换再解码
decodeURIComponent('a+b'.replace(/\+/g, '%20')); // 'a b'
结论:如果输入可能来自 HTML 表单(application/x-www-form-urlencoded),解码前应先将 + 替换为 %20;如果是 encodeURIComponent 产生的字符串,直接 decodeURIComponent 即可。本站工具在解码时会自动兼容这两种空格表示。
常见应用场景
1. 构建查询参数
这是 encodeURIComponent 最高频的用途。永远不要手动拼接查询串,应使用 URLSearchParams 或对每个值单独编码:
// 推荐:URLSearchParams 自动处理编码(现代浏览器原生支持)
const params = new URLSearchParams({ q: '工具 盒子', page: '2' });
const url = `https://example.com/search?${params}`;
// → 'https://example.com/search?q=%E5%B7%A5%E5%85%B7+%E7%9B%92%E5%AD%90&page=2'
// 等价的手动方式
const url2 = `https://example.com/search?q=${encodeURIComponent('工具 盒子')}&page=2`;
2. 编码路径段
URL 路径中的每一段(/ 之间的内容)应使用 encodeURIComponent,但 / 本身不应被编码,因为它代表目录层级:
const category = encodeURIComponent('前端工具/中文');
const slug = encodeURIComponent('encodeURI 区别');
const url = `https://example.com/posts/${category}/${slug}`;
3. mailto 链接的中文主题
邮件链接的 subject 与 body 参数包含中文时必须编码,否则部分邮件客户端会乱码:
const subject = encodeURIComponent('关于工具盒子的反馈');
const body = encodeURIComponent('你好,我想反馈一个问题:');
const mailto = `mailto:support@example.com?subject=${subject}&body=${body}`;
4. Data URI 与内联资源
Data URI 中嵌入文本或二进制时,Base64 是更稳妥的选择;若用纯文本则需对特殊字符编码:
// 内联 SVG 时,# 与 % 等字符必须编码,否则破坏 Data URI 结构
const svg = '<svg xmlns="http://www.w3.org/2000/svg">#text</svg>';
const dataUri = `data:image/svg+xml,${encodeURIComponent(svg)}`;
常见错误与陷阱速查
| 陷阱 | 错误做法 | 正确做法 |
|---|---|---|
| 用 encodeURI 编码参数值 | '?q=' + encodeURI('a&b') → 保留 & 破坏结构 | 用 encodeURIComponent |
| 用 encodeURIComponent 编码整条 URL | 编码掉 :// 导致 URL 失效 | 用 encodeURI 或不编码 |
| 解码表单数据时忽略 + | decodeURIComponent('a+b') → 'a+b' | 先 replace(/\+/g, '%20') 再解码 |
| 重复编码 | 对已编码字符串再次编码,% 变成 %25 | 只编码一次,原始输入才编码 |
| 混用编码与拼接 | 手动拼接 ? & = 易遗漏 | 用 URLSearchParams 自动处理 |
| 依赖已弃用的 escape | escape('工具') 返回 %u5DE5%u5177 | 用 encodeURIComponent(UTF-8 字节编码) |
特别提醒:
escape()函数已被 ECMAScript 标准弃用,它使用%uXXXX形式编码,不符合 RFC 3986,现代代码中应避免使用。
何时不需要手动编码
现代浏览器与 fetch API 在很多场景下会自动处理编码,无需手动干预:
URLSearchParams构造查询串时自动编码参数值a标签的href包含中文时,浏览器会自动编码后再发请求fetch(url)传入包含中文的 URL,浏览器自动编码
但在生成用于展示、存储或拼接的 URL 字符串时,仍需显式编码,因为 JavaScript 字符串本身不会自动转义。判断原则:如果这个 URL 字符串会被原样传递给另一个系统(写入 HTML、存入数据库、拼接到另一个 URL),就显式编码。
小结
URL 编码的核心是按字节、按上下文转义。记住两个函数的分工:encodeURI 保留 URL 骨架、用于整条 URL;encodeURIComponent 编码一切、用于单个组件。遇到表单数据时留意 + 与 %20 的差异,遇到多字节字符时确认使用 UTF-8。掌握这些,就能避开绝大多数 URL 编码陷阱。
读完文章,想立即验证编码效果?打开配套工具,输入任意文本实时查看 encodeURI 与 encodeURIComponent 的差异。