为什么 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,解码时就还原不回去了。