为什么 URL 里不能直接放中文
URL 设计于 1994 年(RFC 1738),那时候只考虑了 ASCII 字符集——也就是英文字母、数字和少数几个符号。URL 的传输和处理涉及到 DNS 解析、HTTP 请求头、代理服务器转发等环节,这些环节都假设 URL 是纯 ASCII 文本。
如果你在 URL 里直接塞一个中文字符,问题就来了:「中」这个字在 UTF-8 下是 3 个字节 0xE4 0xB8 0xAD,但接收端不知道这是 UTF-8 还是 GBK 还是别的编码。不同系统按不同编码去解读,结果就可能乱码或直接报错。
所以 URL 标准规定:非 ASCII 字符必须先转成字节序列,再把每个字节用百分号加两位十六进制表示。这就是为什么「中」变成了 %E4%B8%AD——三个字节各加了百分号前缀。
你在浏览器地址栏里看到中文能正常打开,是因为现代浏览器会自动帮你做编码转换。显示给你看的是友好版本,实际发出去的是编码后的版本。
百分号编码的规则
百分号编码的核心规则很简单:把需要编码的字节写成 %XX 的形式,XX 是这个字节的十六进制值。但关键是搞清楚哪些字符需要编码、哪些不需要。
URL 标准把字符分成了几类:
| 类别 | 字符 | 是否需要编码 |
|---|---|---|
| 非保留字符 | A-Z a-z 0-9 - _ . ~ | 不需要 |
| 保留字符 | : / ? # [ ] @ ! $ & ' ( ) * + , ; = | 看位置,有时需要 |
| 其他 ASCII | 空格、双引号、尖括号等 | 需要 |
| 非 ASCII | 中文、日文、emoji 等 | 必须编码 |
非保留字符在任何地方都不需要编码。保留字符有特殊含义(比如 ? 分隔 path 和 query,& 分隔参数),如果你要在参数值里用这些字符的字面量,就得编码。比如搜索关键词 a&b,不编码的话 & 会被当成参数分隔符。
encodeURI 和 encodeURIComponent 的区别
这是 JavaScript 里最容易搞混的两个函数。核心区别就一句话:encodeURI 保留 URL 结构字符,encodeURIComponent 不保留。
// encodeURI:保留 :/?#&=+@ 等结构字符
encodeURI("https://example.com/search?q=中文")
// → "https://example.com/search?q=%E4%B8%AD%E6%96%87"
// URL 结构完好,只编码了中文
// encodeURIComponent:结构字符也编码
encodeURIComponent("https://example.com/search?q=中文")
// → "https%3A%2F%2Fexample.com%2Fsearch%3Fq%3D%E4%B8%AD%E6%96%87"
// 冒号、斜杠全编码了,URL 结构被破坏
什么时候用哪个?记住了:
- encodeURI:你有一整条 URL,只想把里面的中文和特殊字符编码掉,URL 的结构不能动。比如拼接一个带中文参数的完整链接。
- encodeURIComponent:你在编码 URL 的一部分(通常是参数值),这个值里如果出现
&或=必须被编码,否则会破坏 URL 结构。比如把用户输入的搜索词放进 query string。
实际开发中 encodeURIComponent 用得更多,因为大多数场景是往 URL 里塞参数值。
URL 编码和 Base64 编码有什么不同
这两个经常被搞混,但它们解决的问题完全不一样:
| 对比项 | URL 编码 | Base64 编码 |
|---|---|---|
| 目的 | 让 URL 能安全传输 | 在文本通道里传二进制数据 |
| 输入 | 文本(URL 或参数) | 任意二进制数据 |
| 输出 | 百分号加十六进制 | A-Z a-z 0-9 + / 和 = 填充 |
| 可逆性 | 可逆(decode) | 可逆(decode) |
| 体积变化 | 每个编码字符变 3 倍 | 整体约增大 1/3 |
| 可读性 | 编码后部分可读 | 完全不可读 |
简单说:URL 编码是让文本在 URL 里安全传输,Base64 是让二进制数据在文本通道里安全传输。如果你要把一张图片嵌进 JSON 或 HTML 里,用 Base64;如果你要把中文塞进网址里,用 URL 编码。
常见的 URL 编码陷阱
陷阱一:空格变成 + 还是 %20?
这是最常见的混淆。标准 URL 编码里,空格应该变成 %20。但 HTML 表单提交(application/x-www-form-urlencoded)有一个约定:空格用 + 表示。JavaScript 的 encodeURIComponent 用 %20,而 encodeURI 把空格编码成 %20。但 PHP 的 urlencode 会把空格变成 +。
实际影响:URL 路径部分的空格必须用 %20,用 + 会被当成字面量加号。query string 里的空格用 + 或 %20 都行,但用 %20 更保险。
陷阱二:双重编码
如果一个字符串已经被编码过,再编码一次就会出问题。%E4%B8%AD 再编码一次就变成 %25E4%25B8%25AD,百分号本身被编码了。这在多层代理转发或多次框架处理时容易遇到。解法是只编码一次,或者编码前先检查是否已经被编码。
陷阱三:编码字符集不一致
同一个中文字符,UTF-8 编码是 %E4%B8%AD,GBK 编码是 %D6%D0。如果编码端用 UTF-8、解码端按 GBK 解,就会乱码。现在基本上都统一用 UTF-8 了,但老系统对接时还是要注意这个问题。
陷阱四:井号 # 后面的内容不发给服务器
URL 里 # 后面是 fragment(锚点),浏览器不会把它发给服务器。如果你把参数放在 # 后面又期望服务端能读到,那是读不到的。编码 # 成 %23 后它就变成了普通字符,才会被发送。
常见问题 FAQ
URL 里的空格应该编码成 %20 还是 +?
标准 URL 编码中空格是 %20。加号表示空格是 HTML 表单提交的约定,只在 query string 里有效。路径部分的空格必须用 %20,用 + 会被当作字面量加号。不确定就用 %20,它在哪都安全。
encodeURI 和 encodeURIComponent 到底用哪个?
编码完整 URL 时用 encodeURI,它保留 :/?#&= 这些 URL 结构字符。编码 URL 的某个参数值时用 encodeURIComponent,它会把那些结构字符也编码掉,防止参数值里的特殊字符破坏 URL 结构。简单记:整条 URL 用 encodeURI,单个参数值用 encodeURIComponent。
为什么有些网址里直接有中文也能打开?
现代浏览器会在地址栏显示原始中文方便你阅读,但实际发送请求时会自动把中文编码成百分号格式。你看到的中文只是浏览器的友好显示,真正传输的 URL 仍然是编码后的。用抓包工具看 HTTP 请求就能验证。
URL 编码后的百分号本身怎么编码?
百分号编码成 %25。比如 "100%" 编码后是 "100%25"。这也是双重编码会出问题的原因——已编码字符串里的 % 被当作普通字符再次编码成 %25,解码时就还原不回去了。