拼 URL 这种”五分钟搞定”的事,结果测试环境 OK、生产环境参数丢一半——八成是选错了编码函数。encodeURI、encodeURIComponent、escape 三个长得像的函数做的事完全不同。
三个函数对同一组字符的行为
先看对比表:
| 字符 | escape | encodeURI | encodeURIComponent |
|---|---|---|---|
| 空格 | %20 | %20 | %20 |
+ | + | + | %2B |
& | %26 | & | %26 |
= | = | = | %3D |
? | %3F | ? | %3F |
# | %23 | # | %23 |
/ | / | / | %2F |
: | %3A | : | %3A |
@ | @ | @ | %40 |
| 中文”你” | %u4F60 ❌ | %E4%BD%A0 | %E4%BD%A0 |
三个关键差异:
escape中文输出%uXXXX——非标准,很多服务器不识别encodeURI保留 URL 结构字符(: / ? & = # @)encodeURIComponent全部编码
什么时候用谁
用 encodeURIComponent:编码参数 value
const name = '张三&李四';
const url = `/api/user?name=${encodeURIComponent(name)}`;
// /api/user?name=%E5%BC%A0%E4%B8%89%26%E6%9D%8E%E5%9B%9B
如果这里用 encodeURI,& 不会被编码,后端以为 name=张三、多出一个 李四 参数——数据错位。
用 encodeURI:编码整个 URL(兜底)
const url = 'https://example.com/搜索?q=你好';
fetch(encodeURI(url));
// https://example.com/%E6%90%9C%E7%B4%A2?q=%E4%BD%A0%E5%A5%BD
这里 :、/、? 作为结构字符要保留原样,所以不能用 encodeURIComponent——那会把整个 URL 变成一个字符串。
永远不用 escape
历史包袱,写新代码就当它不存在。
编码拼 URL 的正确顺序
常见错误:先拼后编码——把结构字符一起编了。
错误:
encodeURIComponent(`/api?name=${name}`)
// %2Fapi%3Fname%3D张三 ← 整个 URL 被编成一个字符串
正确:
`/api?name=${encodeURIComponent(name)}`
// /api?name=%E5%BC%A0%E4%B8%89
口诀:结构先拼好,value 再编码。
URLSearchParams:更现代的做法
手动拼字符串容易漏。用内置 API:
const params = new URLSearchParams();
params.set('name', '张三&李四');
params.set('age', 30);
const url = `/api/user?${params.toString()}`;
// /api/user?name=%E5%BC%A0%E4%B8%89%26%E6%9D%8E%E5%9B%9B&age=30
自动处理编码、多值(append 可加同名参数)、+ 空格表单规则。
注意:URLSearchParams 对空格用 +(遵循 form-urlencoded),不是 %20。对大多数服务端没问题,但如果后端严格按 RFC 3986 解析路径,要手动 .replace(/\+/g, '%20')。
路径段里的特殊情况
URL 路径里的斜杠是分隔符,文件名含 / 需要编码:
const filename = 'report/2026Q1.pdf';
const url = `/files/${encodeURIComponent(filename)}`;
// /files/report%2F2026Q1.pdf
如果直接拼,后端会把它当成两段路径。
反过来,如果是子路径,不要编码:
const path = '/reports/2026/Q1';
const url = `/api${path}`; // 对
const url = `/api${encodeURIComponent(path)}`; // 错,斜杠变 %2F
解码对称性
编码用 encodeURIComponent,解码就用 decodeURIComponent。编码用 encodeURI,解码用 decodeURI。混用在大多数情况下没事,但遇到包含已编码百分号(%25)的字符串会出现”多次解码”陷阱。
const a = encodeURIComponent('100%'); // '100%25'
decodeURIComponent(a); // '100%' 正确
decodeURIComponent(decodeURIComponent(a)); // '100' ← 第二次解码失败但 JS 抛出
后端接收时的注意
- Node.js / Express:
req.query自动解码一次,不要再手动decodeURIComponent - Spring Boot:
@RequestParam自动解码,默认 UTF-8 - 老 Java(Tomcat 8 以前):默认 ISO-8859-1,中文要么改
URIEncoding="UTF-8",要么前端双重编码 - PHP:
$_GET已解码
两端都解码会变成 %25 → % → 原文——多了一次就丢了百分号。
排错清单
参数收到是乱码或丢失时,按顺序查:
- 前端用的是
encodeURIComponent还是encodeURI?参数 value 必须用前者 - 后端字符集是不是 UTF-8?Tomcat / IIS 老配置可能不是
- 中间有没有 Nginx / CDN 做了二次解码?
$arg_在 Nginx 里会解一次 - 含
+的值是否被当成空格?路径里+是字面+,query 里是空格 #后面的内容永远不会发到服务端(fragment)
复制到工具里看
把 URL 或参数贴进工具,能实时对比 encodeURI / encodeURIComponent 两种编码结果、解码时高亮非法字符、支持一次多行批处理——改 URL 不再靠猜。