同样是解码,结果却天差地别?可能用了错的函数!

同样是解码,结果却天差地别?可能用了错的函数!

1. 引言

在 Web 开发中,我们经常需要在客户端对 URL 做处理:构造含中文、空格、特殊字符的链接、向服务器传递参数、在地址栏中显示用户输入……如果直接把未经编码的字符串当作 URL,往往会导致浏览器解析出错、请求失败,甚至出现安全隐患。

JavaScript 团队因此在 ECMAScript 规范里引入了一套“百分号编码”(Percent‑encoding)机制,并提供了四个全局函数来辅助:

encodeURI

encodeURIComponent

decodeURI

decodeURIComponent

2. URL 与百分号编码

1. URL的组成

一个典型的 URL(统一资源定位符)可以拆分为以下几部分:

| 协议 | 主机 | 端口 | 路径 | 查询字符串 | 哈希值 |

https://example.com:8080/目录/文件.html?key=值&foo=bar#section

协议(scheme):https://主机与端口(authority):example.com:8080路径(path):/目录/文件.html查询字符串(query):?key=值&foo=bar锚点/哈希(fragment):#section

2. 非 ASCII 字符与保留字符

浏览器与服务器在协议层对 URL 有严格的规范(RFC 3986):

保留字符(Reserved):: / ? # [] @ ! $ & ' () * + , ; =

用于分隔 URL 结构;如果要作为普通内容出现,必须编码。

非 ASCII 字符:如中文、韩文、俄文、空格等,均不能直接出现在 URL 中。

为什么要百分号编码?

百分号编码(Percent‑encoding)将任意字节序列转成 % + 两位十六进制数的形式。

例如,UTF‑8 编码下的中文“中”是三个字节 0xE4 0xB8 0xAD,分别编码后为 %E4%B8%AD。

3. 四大函数对比

函数作用对象编码范围(被编码)encodeURI整条 URI

非 ASCII 字符 + 空格等,但保留 :/?#[]@!$&'()*+,;=

encodeURIComponentURI 组件

几乎所有特殊字符(包括 :/?&=+ 等)

decodeURI整条 URI

解码 % 后对应的所有字符,但不解码保留字编码

decodeURIComponentURI 组件解码所有 % 开头的字节序列

encodeURI 详解

作用场景

当已经有一条完整的 URI(包含协议、主机、路径、查询),只想保证其中的非 ASCII 与空格被正确编码,但又想保留分隔符不被再度编码时,用它。 典型用例:批量把用户输入路径拼进 URL,然后送给 window.location、Ajax 请求。

const raw = 'https://example.com/目录 文件.html?foo=测试&bar=1';

console.log(encodeURI(raw));

// 输出: "https://example.com/%E7%9B%AE%E5%BD%95%20%E6%96%87%E4%BB%B6.html?foo=%E6%B5%8B%E8%AF%95&bar=1"

ASCII 保留字符

encodeURI 时,这些符号不会被编码:

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

┌─────────┐

URI 全串 → encodeURI → 应保留字符────────┐

└──────────────────→ 编码其他(含空格、中文、emoji等)

encodeURIComponent

作用场景

对 URI 的组件(component)进行编码,例如单个查询参数值、单段路径,或者哈希值、单键名等。当只想对某段文本进行完全集中的编码(包括所有符号)时,用它。

const paramValue = '目录 / 文件?测试&123';

console.log(encodeURIComponent(paramValue));

// 输出: "%E7%9B%AE%E5%BD%95%20%2F%20%E6%96%87%E4%BB%B6%3F%E6%B5%8B%E8%AF%95%26123"

与 encodeURI 的比较

encodeURIencodeURIComponent编码的符号范围非 ASCII、空格 等几乎全部特殊字符是否编码 “/”否是是否编码 “?”否是适用场景整条 URLURL 组件(参数、单段路径)

解码函数:decodeURI 与 decodeURIComponent

decodeURI 不解码保留字符

设计初衷:对整条 URI 做解码,只对非保留字符的百分号编码进行解码,不会把保留分隔符意外解码,以免破坏 URL 结构。

行为:遇到 % 后跟的两位十六进制,如果它代表“非保留字符”,就还原;如果它代表上表中的任一保留字符(如 %3F → ?,%26 → &),则保持为原始的 %3F、%26。

decodeURIComponent 全部解码

设计初衷:对单个 URI 组件(component)做完全解码,不管它原本是不是分隔符。

行为:凡是符合 %xx 格式的,都一律还原为对应字符。

举个 🌰

const url = 'https://例子.com/%E7%9B%AE%E5%BD%95%20%E6%96%87%E4%BB%B6.html?name=%E5%BC%A0%E4%B8%89%26%E5%8D%81';

// decodeURI → 保留 ? & = 不被解码

console.log(decodeURI(url));

// "https://例子.com/目录 文件.html?name=张三%26十"

// %E7%9B%AE%E5%BD%95、%20、%E6%96%87%E4%BB%B6、%E5%BC%A0%E4%B8%89 → 被正确还原为中文和空格

// 但 %26 对应的是 &(保留字符),因此不还原,在结果里仍然是字面上的 %26。

// decodeURIComponent → 全部解码

console.log(decodeURIComponent(url));

// "https://例子.com/目录 文件.html?name=张三&十"

// 这里 %26 被还原为 &,于是查询字符串从 "name=张三%26十" 变成了 "name=张三&十",等于是又多加了一个参数分隔符。

为什么结果不一样呢?

decodeURI 和 decodeURIComponent 的不同之处在于,它们处理保留字符的方式。

decodeURI 不会解码组成 URL 语法的一些字符,如 &,它会让 %26 保持原样,而decodeURIComponent 会解码所有百分比编码,包括类似 %26 这样的字符。因此,当使用decodeURI 时,%26 会被保留,而 decodeURIComponent 则将其解码为 &。

保留字符 (Reserved Characters) 与解码行为

根据 RFC 3986 中对 “保留字符” 的定义,下面这些字符用于分隔 URI 的各个部分,不应该被随意转义或还原,否则会破坏 URL 结构:

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

字符含义?开始查询字符串(query)&分隔多个查询参数=参数名与参数值的分隔符...(以及其他分隔路径、哈希等)

何时使用呢?

场景推荐函数备注对整条 URL 进行解码,且希望“?=&”等保持原位

decodeURI()

保留分隔符,保证 URL 结构不被破坏对某个参数值或路径片段 进行解码,不涉及整体结构

decodeURIComponent()

完全还原所有 %xx,适合单独处理组件

切记:不要把 decodeURIComponent 用在整条 URI 上,否则会把原本的查询/片段分隔符都当作普通字符解码,导致 URL 结构被意外破坏。

┌────────────────────────────────────────────────┐

│ encodeURI/URI │

│ 保留 “:/?#[]@!$&'()*+,;=” → 不解码这些 │

│ │

│ "%3F" "%26" "%3D" …… → 保持原样,不变成 "?&="│

└────────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐

│ encodeURIComponent │

│ 对所有 %xx 一律解码 → 无差别,全部还原 │

└──────────────────────────────────────────────┘

4. 为什么要引入“四剑客”?

弱化手写编码的复杂度:早期 Web 开发者若不借助标准函数,就要用正则或手写查表来替换,容易出错。区分“整条 URL”与“URL 组件”:不同场景下,不同字符该不该被编码,必须明确区分,否则会出现双重编码或解码错误。安全与兼容:正确编码能防止 XSS、URL 注入等风险;同时遵循国际化标准(IRI),支持多语言。

5. 单层编码 vs. 双层编码

单层编码

直接对需要的文本调用一次 encodeURI/encodeURIComponent,即可得到合法的 URL 或参数。例如:

const url = base + encodeURIComponent(userInput);

双层编码

有些场景需要先对一段文本做 encodeURIComponent,再把结果拼到更大的字符串里,再对整体调用一次 encodeURI,以保证所有层级都合法:

// 场景:参数中还要包含“路径”这样更复杂的子 URL

const subURL = '/搜索/关键词';

const encodedSub = encodeURIComponent(subURL); // 第一次:所有字符全编码

const final = encodeURI(`https://例子.com/path?redirect=${encodedSub}`);

// 第二次:只对子 URL 编码后残留的 % 等做处理,保留 ? = & 等

示意图

原始子 URL: /搜索/关键词

第一次编码: %2F%E6%90%9C%E7%B4%A2%2F%E5%85%B3%E9%94%AE%E8%AF%8D

拼进 query: ?redirect=%2F%E6%90%9C%E7%B4%A2%2F%E5%85%B3%E9%94%AE%E8%AF%8D

第二次 encodeURI: 保留 % ? = & → 最终安全

相关推荐

C语言函数返回指针
必发365娱乐在线官网

C语言函数返回指针

📅 08-01 👁️ 3553
第2轮:贝拉攻入本届第50球 巴拉圭2-0斯洛伐克
365娱乐app官方版下载

第2轮:贝拉攻入本届第50球 巴拉圭2-0斯洛伐克

📅 07-20 👁️ 9336
国际足联世界杯预选赛
全球最大体育平台365

国际足联世界杯预选赛

📅 08-02 👁️ 2984