一个带中文和空格的 URL,粘到浏览器里有时正常、有时 404,有时参数莫名其妙丢了。问题几乎都出在 URL 编码上。这篇文章讲清为什么要编码、编码规则是什么、以及最容易混的 %20 和 +。
URL 的字符集是有限的。在标准里,只有下面这些字符可以原样出现在 URL 中:
字母 A-Z a-z
数字 0-9
符号 - _ . ~
保留 ! * ' ( ) ; : @ & = + $ , / ? # [ ] %
其他字符(空格、中文、日文、emoji、各种符号)都必须转换成百分号编码:% 后面跟两位十六进制数,这两位数是那个字符的字节值。
关键点:编码的对象是字节,不是字符。所以首先要确定用哪种字符集把字符变成字节——现代标准统一用 UTF-8。
以“中”字为例:
"中"
UTF-8 字节 E4 B8 AD
百分号编码 %E4 %B8 %AD
连起来 %E4%B8%AD
所以“中文”两个字编码后是 %E4%B8%AD%E6%96%87——一个汉字对应三个 %XX,因为 UTF-8 里常用汉字占 3 字节(生僻字和 emoji 是 4 字节,对应四组)。
%XX,别慌,那是正常的 UTF-8 中文。如果变成 2 组,说明用的是 GBK——这往往就是乱码的根源。这是最经典的一个坑。空格的编码有两种写法,用错场景就会出问题:
| 写法 | 用在哪 | 说明 |
|---|---|---|
%20 | URL 的路径部分 | 标准写法,任何地方都安全 |
+ | application/x-www-form-urlencoded 的查询串 | 表单提交格式的历史遗留 |
具体来说:
路径里: /my%20file.txt ✓
/my+file.txt ✗ 服务器会把 + 当成字面加号
查询串: ?q=hello%20world ✓ 一般可用
?q=hello+world ✓ 表单提交格式下,+ 表示空格
%20。它不会错,而 + 是否被解释成空格取决于服务端实现。“双重编码”指数据被编码了两次,% 本身变成了 %25。看到下面这种一串 %25,说明重了:
原文 a b
一次编码 a%20b
二次编码 a%2520b ← % 被编成了 %25
什么时候会发生:
% 再编码排查方法:把可疑参数单独解码一次,看是否还有 %。还有,就是双重编码。
JavaScript 里有两个容易混的函数:
| 函数 | 不编码的字符 | 用在哪 |
|---|---|---|
encodeURI | 保留 :/?#[]@!$&'()*+,;= | 编码整个 URL |
encodeURIComponent | 只保留 -_!~*'() | 编码参数值 |
用错会出现什么:
// 参数里含 & 时,用 encodeURI 会毁掉参数结构
const v = "a&b";
encodeURI(v) → "a&b" ✗ & 没被编码,会被当成分隔符
encodeURIComponent(v) → "a%26b" ✓
// 整个 URL 用 encodeURIComponent 会把 : / 也编码
encodeURIComponent("https://a.com/x")
→ "https%3A%2F%2Fa.com%2Fx" ✗ 整条 URL 废了
记法很简单:编参数值用 encodeURIComponent,编整条 URL 用 encodeURI。
%XX%XX。UTF-8 编码下面,常用汉字一律占 3 字节,所以一个汉字 = 三组。application/x-www-form-urlencoded 解析的框架(多数后端框架的表单解析器)会把 + 当空格;而按 RFC 3986 严格解析的工具会当字面加号。这就是“同一个接口换个框架就出问题”的原因。%XX;+ 出现在本该是空格的位置;%25 出现(双重编码)。命中任何一条,基本可以定位。& 会破坏结构),但这是“正确的一次”,不是“多余的第二次”。判断标准:解码一次后应该得到原始可用的值。