先分流:你要编码的是什么
「URL 编码」这个搜索词背后藏着三种不同的活,选错模式就是事故的开始:
| 你要编码的 | 用什么模式 | 为什么 |
|---|---|---|
参数值(放进 ?key= 后面的内容) |
组件编码(encodeURIComponent) | 值里的 & = ? # 必须转义,否则 a=1&b=2 里的 & 会被当成参数分隔符 |
整条 URL(已有 ? 与 & 结构,只想让中文空格合法化) |
URI 编码(encodeURI) | 保留 URL 结构字符,只编码不合法的字符 |
| 表单提交体(application/x-www-form-urlencoded) | 表单编码 | 空格编码为 + 而不是 %20——历史包袱,详见下文 |
三种模式(URL 编码解码器 都支持,切换即对比):
输入: a=hello world&next=你
component: a%3Dhello%20world%26next%3D%E4%BD%A0 (& = 全转义,当数据传)
uri: a=hello%20world&next=%E4%BD%A0 (结构保留,只编码非法字符)
form: a%3Dhello+world%26next%3D%E4%BD%A0 (空格变 +,表单约定)
一句话记法:编码的是「数据」用 component,编码的是「地址」用 uri,走的是「表单」用 form。
为什么参数必须编码
不编码不是「有时候能跑」,是埋雷。四个事故场景:
&截断参数:值里出现&,服务端把它当下一个参数的开始——q=a&b传过去变成两个参数,前者被截断;#截断查询:#之后的部分是片段标识符,根本不会发到服务器——值里的#会让后面整段消失;- 空格与中文:部分浏览器会兜底编码,但「部分兜底」意味着行为不可预期,跨端不一致;
%本身:值里的%不编码,解码端把它当转义引导符,遇%GG这类非法序列直接报错。
规范的态度很简单:URL 里只保证一部分 ASCII 安全,其余一律百分号编码。%E4%BD%A0 就是「你」——UTF-8 三个字节 E4 BD A0,每字节写成 % 加两位十六进制。它不是乱码也不是加密,是同一内容的合法写法,一个汉字对应三组 %XX 是 UTF-8 的特征。
+ 与 %20:一个历史包袱的两面
空格有两种编码:%20(百分号编码,规范路径)和 +(表单编码,HTML 表单时代的历史约定,沿用至今)。
事故的典型形态:值里本来有 +——电话号码 +86、公式 a+b——服务端按表单规则解码,把 + 还原成空格:+8613800000000 变成 8613800000000。更隐蔽的是签名场景:客户端用 %20 编码空格、服务端按表单规则处理成 +,签名串对不上,校验失败——你以为密钥错了,其实是空格打架。
排查方法:看请求的 Content-Type——application/x-www-form-urlencoded 意味着走 + 约定,JSON body(application/json)里空格就是空格,不参与这套规则。解法:编码端把字面 + 编成 %2B;空格表示两端统一(都用 %20 或都按表单约定)。带表单模式的编码工具可以直接验证两端行为是否一致。
排查清单:解码失败与双重编码
- Invalid URI 报错:
%后面不是两位十六进制(%GG、孤立%、被截断的%E4%BD三缺一)都会让decodeURIComponent抛错。先搜孤立%判断是否截断; - 双重编码:
%被编码成%25,于是%E4变成%25E4——看到%25开头的串,先再解一层。成因多是框架编了一次、代码里又手动编了一次; - 解了一层还是乱码:数一下一个汉字对应几组
%XX——三组是 UTF-8 正常态,还乱码说明内容另有编码层(比如先 Base64 再 URL,逐层剥); - 整条 URL 解码后结构坏了:对整条 URL 用 component 模式解码,
&=还原成数据后参数结构就乱了——解码模式也要与编码时一致。
真话:编码解决传输,不解决语义
百分号编码保证的是「这段内容能安全通过 URL 通道」,它不解决参数语义问题:重复 key(a=1&a=2 谁生效是服务端的事)、数组参数的序列化格式(a[]=1 vs a=1,2 vs a=1&a=2,是框架约定)、空值与 null 的表示——这些用编码工具折腾不出来答案,去查你的框架文档。工具的领地是编码层的排错:把手里这串东西编对、解对、看清里面是什么。
常见问题
encodeURIComponent 和 encodeURI 有什么区别,各自什么时候用?
区别在对待 URL 结构字符的态度:encodeURIComponent 把 & = ? # / 全部转义——它假设你编码的是「一段数据」;encodeURI 保留这些结构字符——它假设你编码的是「一条完整的 URL」。编码参数值必须用 encodeURIComponent,否则值里的 & 会被服务端当成下一个参数的分隔符;编码整条已有结构的 URL 才用 encodeURI。用反了:前者会把 URL 拦腰打断,后者会让参数值被截断。
URL 参数里的中文为什么变成了 %E4%BD%A0 这样的串?
这是百分号编码:URL 规范里只允许一部分 ASCII 字符直接出现,其他内容必须编码。中文字符先按 UTF-8 转成字节——「你」是三个字节 E4 BD A0——每个字节再写成百分号加两位十六进制,就成了 %E4%BD%A0。它不是加密也不是损坏,是同一内容的另一种写法;解码工具一步还原。判断编码是否 UTF-8 的线索:一个汉字对应三组 %XX。
加号解码后为什么变成了空格?
这是表单编码(application/x-www-form-urlencoded)的历史包袱:HTML 表单提交时空格被编码成 + 而不是 %20,这个约定沿用至今。事故场景:值里本来就有 +(电话号码 +86、数学表达式),服务端按表单规则解码就把 + 还原成了空格——签名校验对不上、数据损坏都是这么来的。排查方法:看 Content-Type 是不是 x-www-form-urlencoded;解法是编码端把 + 编成 %2B,或两端统一用 %20 表示空格。
解码时报 Invalid URI 错误怎么回事?
decodeURIComponent 遇到非法百分号序列会直接抛错——% 后面不是两位十六进制(比如 %GG、孤立的 %、被截断的 %E4%BD 三缺一)。常见原因三个:编码层数不对(双重编码时你解了一次还剩一层);内容里有原始 % 字符没被编码成 %25;复制粘贴时编码串被截断。排查顺序:先搜孤立 % 看是否截断,再试再解一次看是否双重编码。