工具指南9-URL编解码工具

你一定见过这种东西:%E4%BD%A0%E5%A5%BD

把它丢进浏览器地址栏,它会变成”你好”。反过来,在URL里写一个中文、空格或者特殊符号,浏览器会自动把它变成这种 %XX 格式。这就是URL编码(URL Encoding),也叫百分号编码(Percent-Encoding)。

对于每天和URL打交道的开发者来说,URL编码是个基础但容易踩坑的话题。特别是当你处理搜索参数、API请求、重定向链接的时候,编码问题经常在最意想不到的地方冒出来。

为什么需要URL编码

URL的设计基于 RFC 3986 标准,它只允许使用 ASCII 字符集的一个子集。具体来说,以下字符可以直接出现在URL中:

  • 字母:A-Za-z
  • 数字:0-9
  • 少数特殊字符:-_.~

这些叫做”未保留字符”(Unreserved Characters)。

另外有一组”保留字符”(Reserved Characters),它们在URL中有特殊含义:

: / ? # [ ] @ ! $ & ' ( ) * + , ; =

比如 ? 分隔路径和查询参数,& 分隔不同的参数,= 连接键和值。这些字符如果作为数据内容出现(而不是作为分隔符),就必须编码。

至于中文、日文、Emoji 这些非 ASCII 字符,根本不在URL允许的字符范围内,必须编码。

编码规则

百分号编码的规则很直接:把字符转成 UTF-8 字节序列,每个字节用 % 加两位十六进制数表示。

举几个例子:

原始字符UTF-8 字节编码结果
空格0x20%20
0xE4 0xBD 0xA0%E4%BD%A0
0xE5 0xA5 0xBD%E5%A5%BD
&0x26%26
=0x3D%3D

一个中文字符在 UTF-8 下通常占 3 个字节,所以编码后变成 9 个字符(3 组 %XX)。这也是为什么包含中文的URL会变得特别长。

空格的特殊处理

空格的编码有两种约定,这是一个容易混淆的点:

  • 百分号编码:空格编码为 %20,用于URL路径和大多数场景
  • 表单编码application/x-www-form-urlencoded):空格编码为 +,专门用于 HTML 表单提交

所以 hello world 在URL路径中应该是 hello%20world,但在表单提交的查询参数中可能是 hello+world。两种写法在不同场景下含义相同,但混用会导致解析错误。

JavaScript 中的编码函数

JavaScript 提供了几组 URL 编码函数,它们的编码范围不同,用错了就会出 bug。

encodeURI vs encodeURIComponent

这是最常用的两个函数,区别在于它们”不编码”的字符范围不同:

const url = "https://example.com/搜索?q=hello world&lang=中文";

console.log(encodeURI(url));
// https://example.com/%E6%90%9C%E7%B4%A2?q=hello%20world&lang=%E4%B8%AD%E6%96%87

console.log(encodeURIComponent(url));
// https%3A%2F%2Fexample.com%2F%E6%90%9C%E7%B4%A2%3Fq%3Dhello%20world%26lang%3D%E4%B8%AD%E6%96%87

encodeURI 保留了URL的结构字符(:/?&= 等),只编码数据部分。适合编码完整URL。

encodeURIComponent 把几乎所有特殊字符都编码了,包括 : / ? & =。适合编码URL中的参数值。

记忆方法:编码整个URL用 encodeURI,编码URL的某个组成部分用 encodeURIComponent

实际应用场景

拼接查询参数时,用 encodeURIComponent

const searchTerm = "React hooks & patterns";
const url = `https://api.example.com/search?q=${encodeURIComponent(searchTerm)}`;
// https://api.example.com/search?q=React%20hooks%20%26%20patterns

如果这里用 encodeURI& 不会被编码,API 会把 patterns 当成另一个参数名,解析就错了。

处理重定向URL时,整个目标URL作为参数值,必须用 encodeURIComponent

const redirectUrl = "https://example.com/callback?token=abc123";
const loginUrl = `https://auth.example.com/login?redirect=${encodeURIComponent(redirectUrl)}`;
// redirect 参数的值中,? 和 = 都被正确编码,不会和外层URL的结构冲突

已经废弃的 escape/unescape

escape()unescape() 是很早期的 API,MDN 已经标注为废弃(deprecated)。它们的问题主要有两个:

  1. 不处理 + 号(不会编码,也不会当作空格解码)
  2. 对非 ASCII 字符使用 %uXXXX 格式而不是 UTF-8 百分号编码,和现代标准不兼容
// 不要用这个
escape("你好");  // "%u4F60%u597D" — 非标准格式
encodeURIComponent("你好");  // "%E4%BD%A0%E5%A5%BD" — 标准 UTF-8 编码

如果在老代码里遇到 escape/unescape,建议逐步替换成 encodeURIComponent/decodeURIComponent

常见坑点

双重编码

这是最常见的问题。当数据经过多次编码时:

const param = "hello world";
const encoded = encodeURIComponent(param);  // "hello%20world"
const doubleEncoded = encodeURIComponent(encoded);  // "hello%2520world"

%20 中的 % 被再次编码成 %25,变成了 %2520。服务端解码一次后得到 %20(字面量),而不是空格。

这种问题经常出现在多层封装的代码中:前端编码了一次,中间件又编码了一次,或者 HTTP 客户端库自动编了一次你又手动编了一次。

排查方法:看到 %25 就要警觉,它意味着某个 % 被编码了,很可能是双重编码。

加号与空格的混淆

前面提到,+ 在表单编码中代表空格,但在标准百分号编码中 + 就是 + 本身。

// 场景:用户搜索 "C++ tutorial"
const query = "C++ tutorial";

// 编码后:C%2B%2B%20tutorial(正确)
encodeURIComponent(query);

// 如果后端用的是表单解码逻辑,
// 它可能把 URL 中的 + 当成空格
// "foo+bar" 被解码成 "foo bar" 而不是 "foo+bar"

解决方案:发送数据前用 encodeURIComponent 统一编码,接收方用对应的解码函数。如果必须兼容表单编码格式,可以在编码后手动替换 %20+

function formEncode(str) {
  return encodeURIComponent(str).replace(/%20/g, "+");
}

前后端编码不一致

前端用 JavaScript 编码,后端用 Python、Java 或 Go 解码。不同语言的默认行为可能不同:

# Python 3
from urllib.parse import quote, quote_plus

quote("hello world")        # "hello%20world"
quote_plus("hello world")   # "hello+world"
// Java
URLEncoder.encode("hello world", "UTF-8");  // "hello+world"(注意:Java默认用 + 表示空格)

Java 的 URLEncoder.encode 遵循的是表单编码规范,空格编码为 +,不是 %20。如果前端发 %20,Java 那边直接用 URLDecoder.decode 可以正确解析(它两种都认)。但反过来,Java 编码的 + 如果传到不做表单解码的前端,就可能出问题。

关键原则:确认前后端使用的编码/解码函数是否匹配,特别是空格的处理方式。

哈希片段中的编码

URL 中 # 后面的部分(fragment/hash)不会发送给服务器,但浏览器仍然会对其中的特殊字符做编码。SPA 路由经常用 hash 存储状态:

// 设置 hash
window.location.hash = encodeURIComponent("tab=设置&page=2");

// 读取时解码
const hash = decodeURIComponent(window.location.hash.slice(1));

注意 window.location.hash 读取时,不同浏览器的行为不完全一致。有些浏览器会自动解码,有些不会。稳妥的做法是手动解码一次,并用 try-catch 包裹(防止格式错误的编码序列抛异常)。

URL 编码的实际应用

API 请求构造

现代前端常用 URLSearchParams 来构建查询参数,它会自动处理编码:

const params = new URLSearchParams({
  q: "React hooks & patterns",
  page: "1",
  lang: "中文"
});

console.log(params.toString());
// q=React+hooks+%26+patterns&page=1&lang=%E4%B8%AD%E6%96%87

注意 URLSearchParams 遵循表单编码规范,空格编码为 +。如果你的后端不支持 + 作为空格,可能需要手动处理。

调试编码问题

遇到编码相关的 bug,最有效的调试方式是逐步解码,看每一层的结果:

function debugDecode(str) {
  let current = str;
  let step = 0;
  while (current !== decodeURIComponent(current)) {
    console.log(`Step ${step}: ${current}`);
    current = decodeURIComponent(current);
    step++;
  }
  console.log(`Final: ${current}`);
}

debugDecode("hello%252520world");
// Step 0: hello%252520world
// Step 1: hello%2520world
// Step 2: hello%20world
// Final: hello world
// 结论:这个字符串被编码了 3 次

处理非标准编码

有时候你会遇到用 GBK、Shift-JIS 等非 UTF-8 编码的 URL(老系统常见)。JavaScript 原生函数只支持 UTF-8 编解码,遇到非 UTF-8 的百分号编码序列会抛错或乱码。

这时可以用 TextDecoder API 手动处理:

function decodeGBK(percentEncoded) {
  // 仅适用于纯百分号编码字符串(如 "%C4%E3%BA%C3"),不处理混合 ASCII 的情况
  const bytes = percentEncoded
    .split("%")
    .filter(Boolean)
    .map(hex => parseInt(hex, 16));

  const decoder = new TextDecoder("gbk");
  return decoder.decode(new Uint8Array(bytes));
}

不过这种情况越来越少了。如果你在维护的系统还在用非 UTF-8 编码,是时候考虑迁移了。

在线工具的便利

理解原理之后,日常开发中最实用的做法是直接用在线工具做快速编解码。比如拿到一串 %E4%BD%A0%E5%A5%BD,想知道内容是什么,打开 AnyFreeTools 的 URL 编解码工具,粘贴进去一键解码。

或者反过来,你需要把一段包含中文和特殊字符的文本编码后拼到URL里,手动调用 encodeURIComponent 然后从控制台复制出来也行,但直接用工具更快,还能同时看到编码前后的对比。

这个工具支持标准的百分号编码和表单编码两种模式,也支持批量处理,对于经常需要调试 URL 参数的开发者来说挺方便。

安全相关

URL编码和安全的关系比表面上看起来更紧密。

URL 编码与 XSS

攻击者可能通过编码来绕过简单的输入过滤。比如直接写 <script> 会被过滤,但写 %3Cscript%3E 可能不会。如果应用先做过滤、再解码,攻击代码就绕过了检查。

正确的做法:先解码,再做输入验证和过滤。或者更好的方式是使用模板引擎的自动转义功能,不要手动拼接 HTML。

目录遍历攻击

%2e%2e%2f 解码后是 ../。如果服务端用URL参数拼接文件路径,攻击者可以通过编码的 ../ 跳出预期目录,访问系统文件。

https://example.com/download?file=%2e%2e%2f%2e%2e%2fetc%2fpasswd

防御方式:对文件路径参数做白名单校验,而不是简单地过滤 ../ 字符串。因为攻击者可以使用多层编码、混合大小写等方式绕过。

开放重定向

URL参数中包含重定向地址时,如果不做校验,攻击者可以构造编码后的恶意链接:

https://trusted.com/login?redirect=https%3A%2F%2Fmalicious.com

用户看到的是 trusted.com 的域名,点击后却被跳转到 malicious.com。解决方式是对 redirect 参数做域名白名单校验。

总结

URL编码是 Web 开发的基础设施之一。核心知识点:

  • 百分号编码将非 ASCII 和保留字符转为 %XX 格式
  • encodeURI 编码整个URL,encodeURIComponent 编码参数值
  • 空格在百分号编码中是 %20,在表单编码中是 +
  • 双重编码是最常见的坑,看到 %25 就要排查
  • URL编码绕过是常见的安全攻击手段,防御要在解码之后做校验

掌握这些知识后,绝大多数URL编码问题都能快速定位和解决。日常快速编解码可以用 AnyFreeTools URL 编解码工具 来提高效率。


本系列其他文章