URL 编码和解码 - 看这一篇就够了

在 Web 开发中,处理 URL 是一个常见且至关重要的任务。由于 URL 中可能含有特殊字符和非 ASCII 字符,确保 URL 的正确性和可传递性就成为了一个挑战。为了解决这个问题,JavaScript 提供了一些内置的方法来进行 URL 编码和解码,其中包括 encodeURI()decodeURI()encodeURIComponent()decodeURIComponent()。本文将深入探讨这些方法的使用、注意事项以及可能出现的异常情况。

URL 编码和解码方法简介

  • encodeURI(): 这个方法用于对整个 URL 进行编码,包括协议、域名、路径和查询参数。它将非 ASCII 字符和特殊字符转换为十六进制格式,确保它们可以在 URL 中安全传输。但需要注意,保留字符(如 :/?& 等)不会被编码,因为它们在 URL 中具有特殊含义。

  • decodeURI():encodeURI() 对应,decodeURI() 用于解码已经被编码的 URL。它将十六进制格式的字符恢复为原始字符,使 URL 可以正常解析。

  • encodeURIComponent(): 与前两者不同,encodeURIComponent() 主要用于编码查询参数等特定部分。除了非 ASCII 字符,它还会对保留字符进行编码,以确保不会被误解为 URL 的一部分。

  • decodeURIComponent(): decodeURIComponent() 用于解码使用 encodeURIComponent() 编码的部分。它将编码的字符还原为原始字符,使我们能够正确获取查询参数等信息。

注意事项和异常情况

注意事项

这些方法都是将需要转义的字符使用 %XX (X代表一个十六进制数字)进行替代(编码方法)或者反向进行替代(解码方法)。
xxxURIComponentxxxURI 的区别是处理的字符集范围不同。

encodeURI 不会转义的字符

A--Z a--z 0--9 - _ . ! ~ * ' ( )
; / ? : @ & = + $ , #

encodeURIComponent 不会转义的字符

A--Z a--z 0--9 - _ . ! ~ * ' ( )

js 复制代码
const set1 = ";/?:@&=+$,#"; // Reserved Characters
const set2 = "-.!~*'()"; // Unreserved Marks
const set3 = "ABC abc 123"; // Alphanumeric Characters + Space

console.log(encodeURI(set1)); // ;/?:@&=+$,#
console.log(encodeURI(set2)); // -.!~*'()
console.log(encodeURI(set3)); // ABC%20abc%20123 (the space gets encoded as %20)

console.log(encodeURIComponent(set1)); // %3B%2C%2F%3F%3A%40%26%3D%2B%24%23
console.log(encodeURIComponent(set2)); // -.!~*'()
console.log(encodeURIComponent(set3)); // ABC%20abc%20123 (the space gets encoded as %20)

encodeURI() & encodeURIComponent()

这两个方法抛出异常的场景相同,有以下两种情况:

  1. 超出字符串长度限制 VM348:1 Uncaught RangeError: Invalid string length
  2. 含有 lone surrogate 非法字符

Invalid string length

原因

这个异常严格上来说不是这两个方法直接导致的,而是字符串本来就有的长度限制。只不过由于编码后长度会增加,可能本来没有超长的字符串转化之后超长了。

长度被限制的原因为 v8 限制字符最大长度 ((1 << 29) - 24) = 536870888。这一点估计很多人不知道,算是个冷知识。

((1 << 29) - 24) = 536870888 = 512M (which is the current browser limit in Chrome too)

h 复制代码
class V8_EXPORT String : public Name {
 public:
  static constexpr int kMaxLength =
      internal::kApiSystemPointerSize == 4 ? (1 << 28) - 16 : (1 << 29) - 24;
js 复制代码
'1'.repeat(536870888).length // 536870888
'1'.repeat(536870889).length // Uncaught RangeError: Invalid string length
'我'.repeat(536870888).length // 536870888
'我'.repeat(536870889).length // Uncaught RangeError: Invalid string length
'😄'.repeat(268435444).length // 536870888
'😄'.repeat(268435445).length // Uncaught RangeError: Invalid string length
'👨‍👦'.repeat(107374177).length // 536870885
'👨‍👦'.repeat(107374178).length // Uncaught RangeError: Invalid string length
处理方法

这么长的字符串一般不太常遇到,而且也没有办处理,所以 try catch 一下就好了。

lone surrogate

原因

这种数据可能存在字符处理的过程中,直接输入基本不会输入这种数据,因为这不是一个能够正常展示的数据。

js 复制代码
// High-low pair OK
encodeURI("\uD800\uDFFF"); // "%F0%90%8F%BF"

// Lone high-surrogate code unit throws "URIError: malformed URI sequence"
encodeURI("\uD800");

// Lone low-surrogate code unit throws "URIError: malformed URI sequence"
encodeURI("\uDFFF");

// High-low pair OK
encodeURIComponent("\uD800\uDFFF"); // "%F0%90%8F%BF"

// Lone high-surrogate code unit throws "URIError: malformed URI sequence"
encodeURIComponent("\uD800");

// Lone high-surrogate code unit throws "URIError: malformed URI sequence"
encodeURIComponent("\uDFFF");

你可能会好奇怎么会有这种数据产生?下面是一些不小心产生了 lone surrogate 数据的操作

js 复制代码
"😄".split(""); // ['\ud83d', '\ude04']; splits into two lone surrogates

// "Backhand Index Pointing Right: Dark Skin Tone"
[..."👉🏿"]; // ['👉', '🏿']
// splits into the basic "Backhand Index Pointing Right" emoji and
// the "Dark skin tone" emoji

// "Family: Man, Boy"
[..."👨‍👦"]; // [ '👨', '‍', '👦' ]
// splits into the "Man" and "Boy" emoji, joined by a ZWJ

// The United Nations flag
[..."🇺🇳"]; // [ '🇺', '🇳' ]
// splits into two "region indicator" letters "U" and "N".
// All flag emojis are formed by joining two region indicator letters

You must be careful which level of characters you are iterating on. For example, split("") will split by UTF-16 code units and will separate surrogate pairs. String indexes also refer to the index of each UTF-16 code unit. On the other hand, @@iterator() iterates by Unicode code points. Iterating through grapheme clusters will require some custom code.

处理方法

那么这种情况有没有办法处理呢?在 chrome 111+ 提供了两个新的方法 toWellFormed() isWellFormed() 来专门处理这个问题。

js 复制代码
const strings = [
  // Lone leading surrogate
  "ab\uD800",
  "ab\uD800c",
  // Lone trailing surrogate
  "\uDFFFab",
  "c\uDFFFab",
  // Well-formed
  "abc",
  "ab\uD83D\uDE04c",
];

for (const str of strings) {
  console.log(str.toWellFormed());
}
// Logs:
// "ab�"
// "ab�c"
// "�ab"
// "c�ab"
// "abc"
// "ab😄c"

这样我们只需要先转化一下再调用就好了。

js 复制代码
const illFormed = "https://example.com/search?q=\uD800";

try {
  encodeURI(illFormed);
} catch (e) {
  console.log(e); // URIError: URI malformed
}

console.log(encodeURI(illFormed.toWellFormed())); // "https://example.com/search?q=%EF%BF%BD"

那如果需要兼容低版本浏览器怎么办呢?try catch 啊,你在想什么好事呢

decodeURI() & decodeURIComponent()

这两个方法抛出异常的场景也相同,有以下两种情况:

  1. 后面没有两个十六进制数字的%
  2. 转义序列未编码有效的 UTF-8 字符

简单来说就是 % 和后面跟着的两个字符组合起来没有编码有效的 UTF-8 字符。

未编码有效的 UTF-8 字符

这种情况会报错是因为这两个方法都是用来解码对应编码方法得到的字符串,所以遇到了 % 又无法进行解析就只能报错了。

js 复制代码
try {
  const a = decodeURI("%E0%A4%A");
} catch (e) {
  console.error(e);
}

// URIError: malformed URI sequence

try {
  const a = decodeURIComponent("%E0%A4%A");
} catch (e) {
  console.error(e);
}

// URIError: malformed URI sequence

处理方法

这种情况的字符串其实挺常见的,为了避免这种报错可以采取下面的措施:

  1. 不对未编码的字符串进行解码
  2. 使用 try catch 捕获异常

最佳实践

根据上面的分析,可以发现,无论是编码还是解码,在无法百分百确定入参可靠性的情况下,一定要在调用方法的时候使用 try catch 捕获异常并处理。

总结

理解和正确使用 encodeURI()decodeURI()encodeURIComponent()decodeURIComponent() 方法对于处理 URL 数据至关重要。这些方法可以确保数据正确传递和解析,但需要在实际使用中遵循注意事项,以保证代码的稳定性和可靠性。只有在遵循最佳实践的基础上,我们才能在 Web 开发中高效地处理 URL。无论是构建网页还是开发 Web 应用程序,对这些方法的理解都是不可或缺的。

参考资料

相关推荐
GuWenyue13 小时前
不用第三方SDK!Vue3原生Fetch实现DeepSeek流式输出,90%前端都会踩的分片解析坑一次性解决
前端·人工智能·llm
独隅13 小时前
前端离线暂停更新策略:Service Worker 与 PWA 实战指南
前端
不如摸鱼去13 小时前
Wot UI 2.3.0 发布:二维码组件来了,Open Wot 与 wot-starter 同步更新
前端·ui·微信小程序·前端框架·uni-app
Cloud_bread14 小时前
从ReAct到自主闭环:Agentic Coding核心执行引擎的技术演进
前端·react.js·前端框架
cidy_9814 小时前
OptMem 使用教程
前端
满栀58514 小时前
原生JS + ECharts 多图表可视化实战:从业务实现到工程化优化
开发语言·javascript·echarts
杉氧14 小时前
用 Compose 挑战交互与动效天花板:ComposeCraftLab 开源实验室全解析
android·前端·kotlin
Canace14 小时前
AI 生成到 90% 突然断了:你的解决方案是?
前端·人工智能
TinssonTai14 小时前
Vite 8 版 Chrome 插件全家桶,popup/options/sidepanel 一次集齐
前端·vue.js
玉鸯14 小时前
让 Agent 面向用户:AG-UI 协议构建 Agent 前端
前端·python·agent