先看一个线上事故级别的例子:
const keyword = "a&b=c";
const url = `/search?q=${keyword}`;
// /search?q=a&b=c
你本想搜「a&b=c」,结果后端收到的 q 是 a,还莫名其妙多了个参数 b=c。因为 & 和 = 在 URL 里是语法字符,它们一到查询串里就被解析成了参数分隔符和赋值符——你的值被拆碎了。
修法只有一步:
const url = `/search?q=${encodeURIComponent(keyword)}`;
// /search?q=a%26b%3Dc
encodeURIComponent 就是干这个的。这篇讲清它是什么、为什么需要、怎么用。
一、它是什么
encodeURIComponent() 是 JS 的全局函数,作用是把一个字符串编码成合法的 URI 组件。
它的规则很简单:把除下面这些字符之外的所有字符,按 UTF-8 字节转成 %XX 形式:
A-Z a-z 0-9 - _ . ! ~ * ' ( )
这个「不编码白名单」只有 9 个符号加字母数字。其余全部转义,包括 & = ? / # 空格 + : @ 等等。
encodeURIComponent("a b&c=中");
// "a%20b%26c%3D%E4%B8%AD"
逐个看:
| 原字符 | 编码后 | 原因 |
|---|---|---|
| 空格 | %20 | 空格在 URL 里不合法 |
& | %26 | 会被当成参数分隔符 |
= | %3D | 会被当成键值分隔符 |
中 | %E4%B8%AD | 非 ASCII,按 UTF-8 三字节编码 |
配套的解码函数是 decodeURIComponent(),把 %XX 还原回原字符:
decodeURIComponent("a%20b%26c%3D%E4%B8%AD");
// "a b&c=中"
二、为什么需要它
1. URL 有「保留字符」,它们不能随便出现在值里
URL 是有语法的,某些字符被占用了固定含义:
| 字符 | 在 URL 里的含义 |
|---|---|
? | 查询串开始 |
& | 参数分隔 |
= | 键值分隔 |
# | 片段(hash)开始 |
/ | 路径分隔 |
+ | 在 query 里常被解释为空格 |
如果你把用户的原始输入直接塞进 URL,这些字符就会被当成语法解析,值被破坏、参数串位、甚至跳到错误的 hash。
编码的本质,就是把「数据」和「语法」区分开:让值里的特殊字符不再有语法意义,老老实实当一个普通字节。%26 对 URL 解析器来说就是普通字符「&」,不会触发分隔。
2. 非 ASCII 和非法字符必须转义
中文、emoji、空格这些字符,在 URL 规范里本来就不允许直接出现(或者行为不确定)。编码把它们转成纯 ASCII 的 %XX 序列,保证 URL 在任何环境下都能正确传输、不被截断或乱码。
encodeURIComponent("你好 world");
// "%E4%BD%A0%E5%A5%BD%20world"
三、怎么用
场景一:拼接查询参数(最常用)
const params = { q: "a&b", page: 1, tag: "前端 教程" };
const query = Object.entries(params)
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join("&");
const url = `/search?${query}`;
// "/search?q=a%26b&page=1&tag=%E5%89%8D%E7%AB%AF%20%E6%95%99%E7%A8%8B"
键和值都要编码,因为键里同样可能出现特殊字符。如果查询参数较多,直接用 URLSearchParams 更省心——它会自动帮你编码:
const params = new URLSearchParams({ q: "a&b", tag: "前端 教程" });
`/search?${params}`;
// "/search?q=a%26b&tag=%E5%89%8D%E7%AB%AF+%E6%95%99%E7%A8%8B"
注意一个细节差异:
URLSearchParams把空格编码成+,而encodeURIComponent编成%20。两者在 query 里通常都能被正确解析,但+只在 query 语义下等于空格,放到路径里就是字面的加号。
场景二:构造路径段
动态拼进路径的部分也要编码,否则 / 会截断路径:
const slug = "vue/router";
const url = `/docs/${encodeURIComponent(slug)}`;
// "/docs/vue%2Frouter" —— %2F 是字面的斜杠,不会变成路径分隔
场景三:拼接完整的跳转地址作为参数
把「一个完整 URL」当作另一个 URL 的参数时,必须整体编码:
const target = "https://example.com/a?x=1&y=2";
const loginUrl = `/login?redirect=${encodeURIComponent(target)}`;
// "/login?redirect=https%3A%2F%2Fexample.com%2Fa%3Fx%3D1%26y%3D2"
这样目标地址里的 ? & = / 都不会破坏外层 URL 的结构。
四、别用混:encodeURI 和 escape
浏览器有三个长得像的函数,用途完全不同:
| 函数 | 不编码的字符 | 用途 |
|---|---|---|
encodeURI | 保留 :/?#[]@!$&'()*+,;= 等结构字符 | 编码一整个 URL |
encodeURIComponent | 只保留 A-Za-z0-9-_.!~*'() | 编码 URL 里的某一段/某个值 |
escape(已废弃) | —— | 不要用,逻辑和 URL 无关 |
关键差异在那几个结构字符:
encodeURI("https://a.com/x y?q=中&n=1");
// "https://a.com/x%20y?q=%E4%B8%AD&n=1"
// ↑ : / ? & = 都保留 —— 因为它编的是「完整 URL」,不能动结构
encodeURIComponent("https://a.com/x?q=中&n=1");
// "https%3A%2F%2Fa.com%2Fx%3Fq%3D%E4%B8%AD%26n%3D1"
// ↑ 结构字符全被编码 —— 因为它把整串当成「一个值」
所以:
- 拼参数、拼路径段 → 一律
encodeURIComponent; - 只给用户输入的空格/中文做「整条 URL 的消毒」→ 才用
encodeURI; - 实践中拼参数用
encodeURIComponent或URLSearchParams,几乎不用encodeURI。
最典型的错误是拿 encodeURI 去编码参数值——它不编 & 和 =,照样把参数拆碎:
`/search?q=${encodeURI("a&b=c")}`;
// "/search?q=a&b=c" —— 没修好!
`/search?q=${encodeURIComponent("a&b=c")}`;
// "/search?q=a%26b%3Dc" —— 这才是对的
五、几个常见的坑
1. 双重编码。 已经编码过的字符串再编一次,% 会被编成 %25:
const once = encodeURIComponent("a b"); // "a%20b"
encodeURIComponent(once); // "a%2520b" —— 编过头了
// 而后端 decodeURIComponent 一次只能还原成 "a%20b",还原不回去
排查这类问题就问一句:这个值之前是否已经编码过? 编码只应发生在「原始数据 → URL」这一次。
2. decodeURIComponent 会抛错。 遇到不完整的 % 序列会抛 URIError:
decodeURIComponent("%E4"); // URIError: URI malformed
拿用户可控的字符串解码时要兜底:
function safeDecode(str) {
try {
return decodeURIComponent(str);
} catch {
return str;
}
}
3. 它不编码 !'()*。 这几个符号在白名单里,严格场景(如某些签名、OAuth)若要求它们也转义,需要手动替换。这也是 encodeURIComponent 和 URLSearchParams 输出不完全一致的原因之一。
4. 空格:%20 还是 +? encodeURIComponent 给 %20,URLSearchParams 给 +。在查询串里 + 等价于空格;但在路径里 + 是字面的加号,不会被解码成空格。跨层传递时留意这一层差异。
六、小结
是什么:encodeURIComponent(str) 把字符串编成合法 URI 组件——除 A-Za-z0-9-_.!~*'() 外全部转成 %XX,配套解码是 decodeURIComponent。
为什么:URL 有语法,? & = # / 等是保留字符。不编码就会被当成语法解析,导致值被破坏、参数错位。编码让「数据」不再有语法含义。
怎么用:
| 场景 | 写法 |
|---|---|
| 拼查询参数 | encodeURIComponent(key) + "=" + encodeURIComponent(value) 或 URLSearchParams |
| 拼路径段 | encodeURIComponent(segment) |
| 把完整 URL 当参数 | encodeURIComponent(fullUrl) |
| 整条 URL 消毒 | 才用 encodeURI(实践很少) |
一句话:只要是把「值」拼进 URL,就用 encodeURIComponent;整串 URL 不要编,用 encodeURI。