从一个真实的 bug 说起
某天,后端同事反映接口收到的参数值不对:前端传了一个包含&符号的字符串作为查询参数,但后端解析出来的值被截断了。
原因很简单:& 在 URL 里是参数分隔符,没经过编码就直接拼进 URL,后端自然会把它当成参数边界解析。
这就是 URL 编码存在的意义——URL 里有一批字符有特殊含义,如果数据本身包含这些字符,必须先编码,才能安全传输。
URL 编码的本质:百分号编码
URL 编码的正式名称是百分号编码(Percent-Encoding),规则很简单:
把字符转成 UTF-8 字节,每个字节用 % 加两位十六进制数表示。
以空格为例:
空格 → UTF-8字节: 0x20 → URL编码: %20
以中文"中"为例:
中 → UTF-8字节: 0xE4 0xB8 0xAD → URL编码: %E4%B8%AD
所以 URL 里看到的 %20 就是空格,%E4%B8%AD%E6%96%87 就是"中文"。
哪些字符需要编码
RFC 3986 定义了 URL 中的"保留字符"和"非保留字符":
- 非保留字符(不需要编码):
A-Z a-z 0-9 - _ . ~ - 保留字符(有特殊含义,作为数据时需要编码):
: / ? # [ ] @ ! $ & ' ( ) * + , ; = - 其他字符(中文、日文、空格、特殊符号等):必须编码
encodeURI 和 encodeURIComponent:傻傻分不清
JavaScript 提供了两个函数,很多人搞不清该用哪个。区分方法只有一条:你编码的是整个 URL,还是 URL 里的某个参数值?
encodeURI:编码整个 URL
encodeURI 会保留 URL 结构字符不编码(因为它们是 URL 的组成部分),只编码真正不合法的字符。
encodeURI("https://example.com/search?q=hello world&lang=zh")
// → "https://example.com/search?q=hello%20world&lang=zh"
// 注意:? & = 都没有被编码,因为它们是 URL 结构的一部分
不会编码的字符: A-Z a-z 0-9 ; , / ? : @ & = + $ - _ . ! ~ * ' ( ) #
encodeURIComponent:编码参数值
encodeURIComponent 会对几乎所有特殊字符编码,包括 ? & = # / 等,适合对单个参数值编码。
const keyword = "hello world & more"
const url = "https://example.com/search?q=" + encodeURIComponent(keyword)
// → "https://example.com/search?q=hello%20world%20%26%20more"
// & 被编码成了 %26,不会干扰参数解析
不会编码的字符: A-Z a-z 0-9 - _ . ! ~ * ' ( )
对比一眼看懂
const str = "a=1&b=2/c?d=中文"
encodeURI(str)
// → "a=1&b=2/c?d=%E4%B8%AD%E6%96%87"
// = & / ? 都保留了
encodeURIComponent(str)
// → "a%3D1%26b%3D2%2Fc%3Fd%3D%E4%B8%AD%E6%96%87"
// = & / ? 全部编码了
结论:拼接参数值时用 encodeURIComponent,几乎不会用错。
开发中最常见的几个坑
坑1:空格编码成了 + 而不是 %20
HTML 表单的 application/x-www-form-urlencoded 格式会把空格编码成 +,而不是 %20。这是历史遗留规范,和标准 URL 编码不同。
表单提交: hello world → hello+world
标准URL编码: hello world → hello%20world
后端解析时要注意区分。PHP 的 urldecode() 会把 + 还原为空格,但 rawurldecode() 不会。
坑2:对同一个值编码了两次
// 第一次编码
const encoded = encodeURIComponent("hello world")
// → "hello%20world"
// 又编码了一次!
const doubleEncoded = encodeURIComponent(encoded)
// → "hello%2520world" (%25 是 % 的编码)
后端收到 %2520,解码一次得到 %20,再解码才能得到空格。这种 double encoding 是接口联调时的常见问题。
坑3:encodeURI 无法编码 #
# 在 URL 里表示锚点,encodeURI 不会对它编码。如果参数值里包含 #,必须用 encodeURIComponent。
encodeURI("https://example.com?tag=#title")
// → "https://example.com?tag=#title" # 没有被编码!
"https://example.com?tag=" + encodeURIComponent("#title")
// → "https://example.com?tag=%23title" 正确
坑4:后端语言的解码函数不统一
不同语言的 URL 解码函数行为略有差异:
# Python
from urllib.parse import unquote, unquote_plus
unquote("hello%20world") # → "hello world" (%20 → 空格)
unquote_plus("hello+world") # → "hello world" (+ → 空格)
// Java
URLDecoder.decode("hello+world", "UTF-8") // → "hello world"(+ 会被解码为空格)
URI.create("hello%20world").getPath() // → "hello world"(标准解码)
urldecode("hello+world") // → "hello world"
rawurldecode("hello+world") // → "hello+world"(+ 不解码)
rawurldecode("hello%20world") // → "hello world"
前后端约定好用哪种编码方式,解码函数要对应匹配。
中文 URL 的处理
浏览器地址栏输入中文网址时,浏览器会自动编码再发出请求。比如访问:
https://example.com/搜索?关键词=中文
实际发出的请求是:
https://example.com/%E6%90%9C%E7%B4%A2?%E5%85%B3%E9%94%AE%E8%AF%8D=%E4%B8%AD%E6%96%87
SEO 角度:中文 URL 对搜索引擎是友好的(百度尤其如此),但服务器日志和代码里看到的是编码后的版本,调试时注意解码再看。
快速验证
遇到一串看不懂的 URL 编码字符,或者需要把中文/特殊字符编码成 URL 安全的格式,可以直接用 toolshu.com 的 URL 编码解码工具。支持 encodeURI 和 encodeURIComponent 两种模式,粘贴即用。



加载中...