DevTool 团队

URL 参数编码怎么选:component、URI 与表单三种模式的判断

URL 参数里带中文就报错、带 & 就被截断?问题不在字符,在没选对编码方式。这篇按「你要编码的是什么」分流:编码参数值用 encodeURIComponent(& = ? # 全部转义),编码整条 URL 用 encodeURI(保留查询结构),处理表单提交体用表单模式(空格变 +)。讲清 %E4%BD%A0 这串东西的来历(UTF-8 三字节的百分号表示)、+ 与 %20 的爱恨情仇(表单编码历史包袱怎么制造签名校验事故)、双重编码与 Invalid URI 报错的排查;最后是真话:编码解决传输安全,不解决参数语义。

先分流:你要编码的是什么

「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;复制粘贴时编码串被截断。排查顺序:先搜孤立 % 看是否截断,再试再解一次看是否双重编码。