链接里的中文变成百分号了:URL 编码那点事
做链接编码解码小工具的时候,我自己先把这事儿搞混了。明明写了 encodeURI,结果链接里的 & 还是原样待着,拼出来的参数被截断。
查了一圈才把这两个函数的区别理清楚。这篇讲讲,顺便说说几个实操上的坑。
一、为什么需要编码
URL 里有些字符是有特殊含义的:? 分隔路径和参数,& 分隔参数,# 是锚点,= 连接键值。
那你参数值里正好有个 & 怎么办?比如搜索关键词是 C++ & Rust——直接拼进去,服务器会以为 & 后面是下一个参数,关键词被截断了。
所以需要把这些有特殊含义的字符转成百分号加十六进制的形式,这就是 URL 编码(也叫百分号编码)。
encodeURIComponent('C++ & Rust')
// "C%2B%2B%20%26%20Rust"
+ 变成 %2B,空格变成 %20,& 变成 %26,这样就不会被误读了。
二、两个函数的区别
这是最容易搞混的地方:
| 不编码的字符 | 用途 | |
|---|---|---|
encodeURI |
保留 ; , / ? : @ & = + $ # 这些 URL 语法字符 |
编码整个 URL |
encodeURIComponent |
把这些也全部编码掉 | 编码参数值 |
举例子最直观:
const url = 'https://example.com/search?q=hello&page=1'
encodeURI(url)
// "https://example.com/search?q=hello&page=1" 几乎没变,因为都是合法字符
encodeURIComponent(url)
// "https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dhello%26page%3D1"
看到了吧——encodeURIComponent 把 :// 都编码了,所以它绝对不能用来处理整个 URL,那样得到的不是链接了。
正确的用法是整体用 encodeURI、参数值用 encodeURIComponent:
const keyword = 'C++ & Rust'
const url = `https://example.com/search?q=${encodeURIComponent(keyword)}&page=1`
// "https://example.com/search?q=C%2B%2B%20%26%20Rust&page=1"
一句话记:拼进 URL 参数里的每一段值,都用 encodeURIComponent。
三、它们俩都不管的字符
有意思的是,有几个字符两个函数都不编码:
encodeURIComponent("A-Z a-z 0-9 - _ . ! ~ * ' ( )")
// "A-Z%20a-z%200-9%20-_%20.!~*'()" 只有空格被编码了
! ~ * ' ( ) 这几个原样保留。绝大多数场景无所谓,但如果你要严格编码(比如做签名),得手动补一刀:
function strictEncode(str) {
return encodeURIComponent(str).replace(/[!'()*]/g, c =>
'%' + c.charCodeAt(0).toString(16).toUpperCase()
)
}
四、解码会突然报错
写解码工具的时候测试,输入一串乱码,页面直接崩了:
decodeURIComponent('%')
// URIError: URI malformed
decodeURIComponent 遇到不合法的百分号序列会抛异常,不是返回原字符串。所以解码用户输入必须包 try/catch:
function safeDecode(str) {
try {
return decodeURIComponent(str)
} catch (e) {
return str // 解不了就原样返回,别让页面崩了
}
}
这个在做工具页面时特别重要——用户粘贴什么都可能有,一个百分号就能让整个页面白屏。
五、用 URLSearchParams 拼参数,别手写字符串
以前我拼参数是这样的:
const url = `/search?q=${encodeURIComponent(kw)}&page=${page}&size=${size}`
又长又容易漏编码。现在直接用 URLSearchParams:
const params = new URLSearchParams({
q: 'C++ & Rust',
page: 1,
size: 20,
})
const url = `/search?${params}`
// "/search?q=C%2B%2B+%26+Rust&page=1&size=20"
它自动帮你编码,代码干净多了。
但这里有个坑:你会发现上面空格变成了 + 而不是 %20。因为 URLSearchParams 遵循表单提交的编码规则,toString() 时把空格输出成 +。
这在绝大多数情况没问题(服务端都认),但如果你要跟严格按 RFC 3986 解析的后端对接,得自己换回来:
const url = `/search?${params.toString().replace(/\+/g, '%20')}`
反过来解析也一样,URLSearchParams 能正确读取 + 和 %20 两种写法,不用担心。
六、几个容易踩的点
- 别整个 URL 一起编码。这是最常见的错误,结果就是一堆
%3A%2F%2F,链接废了。 - 别重复编码。已经编码过一次的字符串再编一次,
%20会变成%2520(%自己被编码成%25),解一次码解不回来。做工具的时候最好加个提示,或者提供"只解码一次"的选项。 +在不同上下文含义不同。在 query string 里它表示空格,但在 path 里它就是个普通的加号。所以a+b放在路径里和放在参数里,解码结果可能不一样。- 中文编码是 UTF-8。
encodeURIComponent('中')得到%E4%B8%AD,这是"中"的 UTF-8 三个字节。前后端编码方式要一致,不然解出来是乱码。 - 编码不等于加密。
%E4%B8%AD看着像密文,其实谁都能解,别拿它当安全手段。 - 长度限制。URL 有长度上限(各浏览器不同,一般几千字符),超长的参数别塞 URL 里,改用 POST。
文章总结
URL 编码这事儿规则不复杂,就一条主线:整体链接用 encodeURI,拼进参数的值用 encodeURIComponent。真正的坑在细节上——decodeURIComponent 遇到非法输入会抛异常必须 catch、URLSearchParams 会把空格输出成 +、以及别重复编码。
- 点赞
- 收藏
- 关注作者
评论(0)