一、URL 为什么需要编码
URL(统一资源定位符)的设计初衷是用于"指代互联网上的资源",它最早只支持 ASCII 字符集里的一部分可打印字符。具体来说,URL 里只能安全地出现:
- 字母
A-Z a-z - 数字
0-9 - 少数保留字符:
- _ . ~等等
但现实世界里我们要传的数据远远不止这些。中文、空格、emoji、特殊符号(&、=、?、#)——这些都不能直接放进 URL 里。原因有两点:
- ASCII 范围之外的字符(比如中文)无法用单字节表示
- 某些字符在 URL 里有特殊含义,比如
?表示查询参数开始,&表示参数分隔,如果数据里直接出现这些字符,解析器会搞混。
所以 URL 编码(也叫百分号编码,Percent-Encoding)出现了:把不安全的字符用一个 % 加两位十六进制数表示。
二、百分号编码的原理
规则很简单:
- 把字符按某种字符集(通常是 UTF-8)编码成字节序列
- 每个字节写成
%XX的形式,XX 是两位十六进制数
举几个例子:
| 原始字符 | UTF-8 字节 | URL 编码 |
| 空格 | 0x20 | %20 |
| 中 | 0xE4 0xB8 0xAD | %E4%B8%AD |
| 文 | 0xE6 0x96 0x87 | %E6%96%87 |
| & | 0x26 | %26 |
| = | 0x3D | %3D |
| + | 0x2B | %2B |
所以 URL https://example.com/搜索?q=hello world 编码后可能变成:
https://example.com/%E6%90%9C%E7%B4%A2?q=hello%20world
可以看到,一个中文字符在 URL 里会变成 9 个字符(3 个字节,每个 %XX 3 个字符),这就是为什么带中文的链接看起来特别长。
三、%20 和 + 的区别
这是最容易让人困惑的一个点:空格在 URL 里既可以编码成 %20,也可以编码成 +,两者有什么区别?
答案在于上下文不同:
| 编码方式 | 出现的位置 | 标准 |
%20 | URL 的任何部分(路径、查询、片段) | RFC 3986(通用 URL 标准) |
+ | 仅查询字符串里 | HTML 表单提交规范 |
具体来说,当你用 提交表单时,浏览器会按 application/x-www-form-urlencoded 格式编码数据,这个格式规定空格编码成 +。这个规范比 RFC 3986 早,所以保留了下来。
举个例子:
| 原始数据 | 在查询字符串里 | 在路径里 |
hello world | hello+world | hello%20world |
a=b c | a=b+c | a%3Db%20c |
实际开发中要注意:
- 后端解析查询字符串时,通常会把
+自动还原成空格(比如 PHP 的$_GET、Node.js 的querystring.parse) - 但解析路径时,
+不会被还原成空格,要手动处理
为了避免歧义,建议统一用 %20,它在任何位置都表示空格,不会出错。
四、encodeURI vs encodeURIComponent
JavaScript 提供了两个编码函数,很多人分不清它们的区别。
encodeURI
用于编码完整的 URL,它不会编码 URL 里本身就有的特殊字符:
encodeURI("https://example.com/search?q=hello world&lang=zh")
// 结果:https://example.com/search?q=hello%20world&lang=zh
可以看到 :、/、?、=、& 这些保留字符都没被编码,只有空格被编码了。这是因为这些保留字符是 URL 结构的一部分,编码后 URL 就坏了。
encodeURIComponent
用于编码 URL 里的单个参数值,它会编码所有非字母数字字符(除了 - _ . ! ~ * ' ( )):
encodeURIComponent("hello world&foo=bar")
// 结果:hello%20world%26foo%3Dbar
这里 & 和 = 都被编码了。如果你用 encodeURI 编码这个字符串,& 和 = 不会被编码,传到服务器就会被错误地解析成两个参数。
对比表
| 字符 | encodeURI 是否编码 | encodeURIComponent 是否编码 |
| 空格 | 是 | 是 |
/ | 否 | 是 |
? | 否 | 是 |
& | 否 | 是 |
= | 否 | 是 |
# | 否 | 是 |
: | 否 | 是 |
口诀:
- 编码整个 URL → 用
encodeURI - 编码某个参数值 → 用
encodeURIComponent
五、常见 URL 编码表
| 字符 | 编码 | 字符 | 编码 |
| 空格 | %20 | # | %23 |
! | %21 | $ | %24 |
& | %26 | ' | %27 |
( | %28 | ) | %29 |
* | %2A | + | %2B |
, | %2C | / | %2F |
: | %3A | ; | %3B |
= | %3D | ? | %3F |
@ | %40 | [ | %5B |
] | %5D | 中 | %E4%B8%AD |
六、实战例子
假设你要构造一个搜索 URL,参数 keyword 的值是 Python & Java 教程:
错误做法(直接拼接):
https://example.com/search?q=Python & Java 教程
服务器会以为 q=Python 是一个参数,后面的 Java 教程 是另一个参数,解析直接乱掉。
正确做法(用 encodeURIComponent):
const keyword = "Python & Java 教程";
const url = "https://example.com/search?q=" + encodeURIComponent(keyword);
// 结果:https://example.com/search?q=Python%20%26%20Java%20%E6%95%99%E7%A8%8B
这样服务器才能正确还原出 Python & Java 教程。
七、容易踩的坑
- 编码两次:有些框架会自动编码一次,如果你又手动编码一次,就会出现
%2520这种双重编码。解码时也要相应地解码两次。 - 大小写:
%20和%20等价,但有些系统对大小写敏感,建议统一用大写。 - 保留字符的歧义:
/在路径里是分隔符,在参数值里是普通字符。如果参数值里包含/,要用encodeURIComponent编码成%2F。 - URL 里出现中文:虽然现代浏览器地址栏里能看到中文,但实际传输时已经被编码了。复制链接时编码可能会丢失,导致分享出去的链接打不开。
八、实践推荐
日常开发中 URL 编码解码的场景非常多——构造 API 请求、解析 query 参数、处理前端路由等等。手写 JS 代码调试太麻烦,直接用在线工具最方便:
- URL 编码/解码工具:https://52tool.net/tools/code/urlEncode
支持 encodeURI、encodeURIComponent、UTF-8/GBK 等多种编码切换,实时预览结果,还能一键解码乱码 URL,是前后端联调时的必备工具。