HandyTools Hub

← 全部指南

URL 编码完全指南:什么时候用 encodeURIComponent,什么时候不用

2026-08-06

在浏览器地址栏里输入一个带中文或空格的网址,回车之后它往往会变成一串 %E4%B8%AD%E6%96%87 这样的字符。这就是 URL 编码(也叫百分号编码,percent-encoding)。几乎每个 Web 开发者都被它坑过:拼接的查询参数到了后端变成乱码、解码两次得到一堆 %25、或者一个 & 符号把整个参数表搅乱。这篇文章把 URL 编码的规则讲透,并给出明确的使用建议:什么时候该编码、该用哪个函数、什么时候千万别编码。

为什么 URL 需要编码

URL 的设计初衷是能用最普通的 ASCII 字符集书写和传输。但一个 URL 里既要有结构符号(:/?&=#),又要能装下任意数据,冲突就来了:如果参数值本身含有 &,解析器怎么知道它是参数分隔符还是数据的一部分?答案就是编码——把”数据”里的特殊字符换成 % 加上两位十六进制,这样它们就不会再被误读为结构符号。

保留字符与非保留字符

RFC 3986 把字符分成两类。非保留字符(unreserved)是字母、数字以及 -_.~,它们出现在 URL 任何位置都不需要编码。保留字符(reserved)是 : / ? # [ ] @! $ & ' ( ) * + , ; = 这一组,它们在 URL 里有结构含义:当它们作为数据出现时就必须编码,作为分隔符出现时则保持原样。

注意这个规则是”按角色编码”,不是”按字符编码”。同一个 /,在路径里是合法的分隔符,不需要动;但出现在查询参数值里就必须写成 %2F。这正是下面两个函数区别的根源。

查询参数为什么必须编码

看一个具体例子。假设你要构造这样的搜索链接:

https://example.com/search?q=Tom & Jerry&page=1

这里有两个问题:空格在 URL 里是非法字符,& 会被解析成参数分隔符。后端收到的将是 q=Tom、一个名叫 Jerry 的空参数和 page=1,完全不是你想要的结果。正确做法是对值编码:

https://example.com/search?q=Tom%20%26%20Jerry&page=1

空格变成 %20& 变成 %26,解析器就能正确还原出 Tom & Jerry。想快速验证编码结果,可以把文本粘贴到 URL 编码解码工具,它同时提供”完整 URL”和”参数值”两种模式,正好对应下面要讲的两个函数。

encodeURI 还是 encodeURIComponent

JavaScript 提供了两个编码函数,它们的区别就一句话:encodeURI 编码整个 URL,encodeURIComponent 编码 URL 的一个片段

encodeURI 假设你给它的是一个完整 URL,所以它不会碰 : / ? & = # 这些结构字符。encodeURIComponent 假设你给它的是一段值,所以除了非保留字符之外几乎全部编码:

encodeURI('https://example.com/search?q=汤姆猫 & 杰瑞鼠');
// https://example.com/search?q=%E6%B1%A4%E5%A7%86%E7%8C%AB%20&%20%E6%9D%B0%E7%91%9E%E9%BC%A0
// 结构字符 ? & = 都保留了,但参数值里的 & 也没编码——仍然是错的!

encodeURIComponent('汤姆猫 & 杰瑞鼠');
// %E6%B1%A4%E5%A7%86%E7%8C%AB%20%26%20%E6%9D%B0%E7%91%9E%E9%BC%A0

经验法则:拼接查询参数永远用 encodeURIComponent

const url = `https://example.com/search?q=${encodeURIComponent(keyword)}&page=${page}`;

encodeURI 只适合一种场景:你手里已经有一个拼好的、结构正确的完整 URL,只想把里面的中文和空格处理掉。更现代的做法是干脆用 URLSearchParams,它自动处理编码:

const params = new URLSearchParams({ q: 'Tom & Jerry', page: '1' });
params.toString(); // q=Tom+%26+Jerry&page=1

空格是 %20 还是 +:一段历史

你可能注意到上面 URLSearchParams 把空格编成了 +,而 encodeURIComponent 编成了 %20。两种都对,但来源不同。+ 表示空格来自早期 HTML 表单的 application/x-www-form-urlencoded 编码规则(就是网页表单 GET 提交时的格式),查询字符串沿袭了这个传统;而 %20 是 RFC 3986 标准百分号编码的写法,可以用在 URL 的任何部分。

实际影响在于解码端:+ 只有在查询字符串(或表单数据)语境下才会被还原成空格,在路径里 + 就是字面意义的加号。所以文件下载链接里如果文件名含空格,用 %20 更安全;查询参数里两者皆可,绝大多数服务器都认。URLSearchParamsURL 对象内部能正确处理这两种形式,这也是推荐用它们而不是手工拼字符串的原因之一。

二次编码:最常见的坑

二次编码是指一段已经被编码过的文本又被编码了一遍。%20 里的 % 被编码成 %25,于是空格变成 %2520。后端解码一次得到的是 %20 而不是空格——页面显示乱码、路由 404、签名验证失败,都可能源于此。

典型触发场景有三个:一是前端编码一次、框架(比如某些路由库)又自动编码一次;二是把 location.href 里已经编码过的 URL 整体再扔进 encodeURIComponent;三是 Nginx、CDN 等中间层做了一次”好心”的规范化。排查方法很简单:在最终发出的 URL 里搜 %25,如果出现了,几乎可以肯定哪里编了两次。修复原则是只在最后一环编码一次,上游传递的全程保持原始字符串。拿到一个可疑 URL 时,可以用 URL 解析工具 把它拆成协议、主机、路径和查询参数逐项检查,哪一段还带着 % 一目了然。

中文 URL 怎么处理

中文(以及任何非 ASCII 字符)在 URL 里没有合法形态,必须先按 UTF-8 转成字节序列,再对每个字节做百分号编码。一个汉字通常占 3 个 UTF-8 字节,所以”中”会变成 %E4%B8%AD。现代浏览器的地址栏会自动做这件事,你输入中文网址也能正常访问;但当你用代码发请求时,就得自己处理——还是那条规则:值用 encodeURIComponent,或者交给 URLSearchParams

另一个相关场景是域名。中文域名不走百分号编码,而是用 Punycode 转成 xn-- 开头的 ASCII 形式,这是 DNS 层面的规则,和路径编码互不干扰。

最后提一个容易混淆的概念:URL 编码和 Base64 是两回事。URL 编码解决”字符能不能放进 URL”,Base64 解决”二进制数据怎么变成可打印文本”。有些场景两者会叠加使用——比如把一段 JSON 先 Base64 编码(可用 Base64 编解码工具 试),再把结果里可能出现的 +/= 做 URL 编码放进参数。理解每一层在解决什么问题,就不会再把它们搅在一起了。

速查建议

  • 拼查询参数:用 URLSearchParams,或手工 encodeURIComponent 每个值;
  • 处理完整 URL:用 encodeURI,但前提是这个 URL 的值部分已经各自编码好了;
  • 解码优先用 decodeURIComponent 处理单个参数值,而不是对整个 URL 调 decodeURI
  • 看到 %25 就是二次编码,去找到多编一次的那一环;
  • 文件名和路径里的空格用 %20,别用 +