2021 年 12 月 12 日

URL 对象

内置的 URL 类提供了一个方便的接口来创建和解析 URL。

没有网络方法需要确切的 URL 对象,字符串就足够了。所以从技术上讲,我们不必使用 URL。但有时它确实很有帮助。

创建 URL

创建新的 URL 对象的语法

new URL(url, [base])
  • url – 完整的 URL 或仅路径(如果设置了 base,请参见下文),
  • base – 可选的基础 URL:如果设置了该参数并且 url 参数只有路径,则 URL 是相对于 base 生成的。

例如

let url = new URL('https://javascript.js.cn/profile/admin');

这两个 URL 相同

let url1 = new URL('https://javascript.js.cn/profile/admin');
let url2 = new URL('/profile/admin', 'https://javascript.js.cn');

alert(url1); // https://javascript.js.cn/profile/admin
alert(url2); // https://javascript.js.cn/profile/admin

我们可以轻松地基于相对于现有 URL 的路径创建新的 URL

let url = new URL('https://javascript.js.cn/profile/admin');
let newUrl = new URL('tester', url);

alert(newUrl); // https://javascript.js.cn/profile/tester

URL 对象允许我们立即访问其组件,因此它是解析 URL 的一种好方法,例如:

let url = new URL('https://javascript.js.cn/url');

alert(url.protocol); // https:
alert(url.host);     // javascript.info
alert(url.pathname); // /url

以下是 URL 组件的速查表

  • href 是完整的 URL,与 url.toString() 相同
  • protocol 以冒号字符 : 结尾
  • search – 一串参数,以问号 ? 开头
  • hash 以井号 # 开头
  • 如果存在 HTTP 身份验证,还可能存在 userpassword 属性:http://login:[email protected](上面没有画出,很少使用)。
我们可以将 URL 对象传递给网络(以及大多数其他)方法,而不是字符串

我们可以在 fetchXMLHttpRequest 中使用 URL 对象,几乎在所有需要 URL 字符串的地方都可以使用。

通常,URL 对象可以传递给任何方法,而不是字符串,因为大多数方法将执行字符串转换,将 URL 对象转换为包含完整 URL 的字符串。

SearchParams “?…”

假设我们想要创建一个具有给定搜索参数的 URL,例如 https://google.com/search?query=JavaScript

我们可以在 URL 字符串中提供它们

new URL('https://google.com/search?query=JavaScript')

…但是如果参数包含空格、非拉丁字母等,则需要对其进行编码(下面将详细介绍)。

因此,有一个 URL 属性用于此:url.searchParams,一个类型为 URLSearchParams 的对象。

它为搜索参数提供了方便的方法

  • append(name, value) – 按 name 添加参数,
  • delete(name) – 按 name 删除参数,
  • get(name) – 按 name 获取参数,
  • getAll(name) – 获取具有相同 name 的所有参数(这是可能的,例如 ?user=John&user=Pete),
  • has(name) – 检查参数是否存在于 name 中,
  • set(name, value) – 设置/替换参数,
  • sort() – 按名称对参数进行排序,很少需要,
  • …它也是可迭代的,类似于 Map

包含空格和标点符号的参数示例

let url = new URL('https://google.com/search');

url.searchParams.set('q', 'test me!'); // added parameter with a space and !

alert(url); // https://google.com/search?q=test+me%21

url.searchParams.set('tbs', 'qdr:y'); // added parameter with a colon :

// parameters are automatically encoded
alert(url); // https://google.com/search?q=test+me%21&tbs=qdr%3Ay

// iterate over search parameters (decoded)
for(let [name, value] of url.searchParams) {
  alert(`${name}=${value}`); // q=test me!, then tbs=qdr:y
}

编码

有一个标准的 RFC3986 定义了哪些字符允许在 URL 中使用,哪些字符不允许。

不允许使用的字符必须进行编码,例如非拉丁字母和空格 - 替换为它们的 UTF-8 代码,并在前面加上 %,例如 %20(空格可以用 + 编码,出于历史原因,但这是一种例外情况)。

好消息是 URL 对象会自动处理所有这些。我们只需提供所有未编码的参数,然后将 URL 转换为字符串。

// using some cyrillic characters for this example

let url = new URL('https://ru.wikipedia.org/wiki/Тест');

url.searchParams.set('key', 'ъ');
alert(url); //https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D1%81%D1%82?key=%D1%8A

如您所见,url 路径中的 Тест 和参数中的 ъ 都被编码了。

URL 变长了,因为每个西里尔字母在 UTF-8 中用两个字节表示,所以有两个 %.. 实体。

编码字符串

URL 对象出现之前,人们使用字符串来表示 URL。

现在,URL 对象通常更方便,但字符串仍然可以使用。在许多情况下,使用字符串可以使代码更短。

但是,如果我们使用字符串,我们需要手动编码/解码特殊字符。

为此,有内置函数

一个自然的问题是:“encodeURIComponentencodeURI 之间有什么区别?我们什么时候应该使用哪一个?”

如果我们看一下上面的图片中分成组件的 URL,就很容易理解了。

https://site.com:8080/path/page?p1=v1&p2=v2#hash

如我们所见,:?=&# 等字符在 URL 中是允许的。

另一方面,如果我们看一下单个 URL 组件,例如搜索参数,这些字符必须进行编码,以避免破坏格式。

  • encodeURI 只编码在 URL 中完全禁止的字符。
  • encodeURIComponent 编码相同的字符,此外还编码 #$&+,/:;=?@

因此,对于整个 URL,我们可以使用 encodeURI

// using cyrillic characters in url path
let url = encodeURI('http://site.com/привет');

alert(url); // http://site.com/%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82

而对于 URL 参数,我们应该使用 encodeURIComponent

let music = encodeURIComponent('Rock&Roll');

let url = `https://google.com/search?q=${music}`;
alert(url); // https://google.com/search?q=Rock%26Roll

将其与 encodeURI 进行比较

let music = encodeURI('Rock&Roll');

let url = `https://google.com/search?q=${music}`;
alert(url); // https://google.com/search?q=Rock&Roll

正如我们所见,encodeURI 不会对 & 进行编码,因为这在整个 URL 中是一个合法的字符。

但我们应该在搜索参数内部对 & 进行编码,否则,我们会得到 q=Rock&Roll - 这实际上是 q=Rock 加上一些模糊的参数 Roll。并非预期结果。

因此,我们应该只对每个搜索参数使用 encodeURIComponent,以便将其正确地插入到 URL 字符串中。最安全的做法是对名称和值都进行编码,除非我们绝对确定它只包含允许的字符。

URL 相比的编码差异

URLURLSearchParams 基于最新的 URI 规范:RFC3986,而 encode* 函数基于过时的版本 RFC2396

有一些区别,例如 IPv6 地址的编码方式不同。

// valid url with IPv6 address
let url = 'http://[2607:f8b0:4005:802::1007]/';

alert(encodeURI(url)); // http://%5B2607:f8b0:4005:802::1007%5D/
alert(new URL(url)); // http://[2607:f8b0:4005:802::1007]/

正如我们所见,encodeURI 替换了方括号 [...],这是不正确的,原因是:在 RFC2396(1998 年 8 月)发布时,IPv6 URL 还不存在。

这种情况很少见,encode* 函数在大多数情况下都能正常工作。

教程地图

评论

在评论之前请阅读…
  • 如果您有任何改进建议,请提交 GitHub 问题或拉取请求,而不是评论。
  • 如果您无法理解文章中的某些内容,请详细说明。
  • 要插入几行代码,请使用 <code> 标签,对于多行代码,请将其包装在 <pre> 标签中,对于超过 10 行的代码,请使用沙盒(plnkrjsbincodepen…)。