Javascript URL 對象

2023-02-17 10:57 更新

內(nèi)建的 URL 類提供了用于創(chuàng)建和解析 URL 的便捷接口。

沒有任何一個(gè)網(wǎng)絡(luò)方法一定需要使用 URL 對象,字符串就足夠了。所以從技術(shù)上講,我們并不是必須使用 URL。但是有些時(shí)候 URL 對象真的很有用。

創(chuàng)建 URL 對象

創(chuàng)建一個(gè)新 URL 對象的語法:

new URL(url, [base])
  • ?url? —— 完整的 URL,或者僅路徑(如果設(shè)置了 base),
  • ?base? —— 可選的 base URL:如果設(shè)置了此參數(shù),且參數(shù) ?url? 只有路徑,則會根據(jù)這個(gè) ?base? 生成 URL。

例如:

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

下面這兩個(gè) URL 是一樣的:

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

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

我們可以根據(jù)相對于現(xiàn)有 URL 的路徑輕松創(chuàng)建一個(gè)新的 URL:

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

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

URL 對象立即允許我們訪問其組件,因此這是一個(gè)解析 url 的好方法,例如:

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

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

這是 URL 組件的備忘單:


  • ?href? 是完整的 URL,與 ?url.toString()? 相同
  • ?protocol? 以冒號字符 ?:? 結(jié)尾
  • ?search? —— 以問號 ??? 開頭的一串參數(shù)
  • ?hash? 以哈希字符 ?#? 開頭
  • 如果存在 HTTP 身份驗(yàn)證,則這里可能還會有 ?user? 和 ?password? 屬性:?http://login:password@site.com?(圖片上沒有,很少被用到)。

我們可以將 ?URL? 對象傳遞給網(wǎng)絡(luò)(和大多數(shù)其他)方法,而不是字符串

我們可以在 fetch 或 XMLHttpRequest 中使用 URL 對象,幾乎可以在任何需要 URL 字符串的地方都能使用 URL 對象。

通常,URL 對象可以替代字符串傳遞給任何方法,因?yàn)榇蠖鄶?shù)方法都會執(zhí)行字符串轉(zhuǎn)換,這會將 URL 對象轉(zhuǎn)換為具有完整 URL 的字符串。

SearchParams “?…”

假設(shè),我們想要創(chuàng)建一個(gè)具有給定搜索參數(shù)的 url,例如:https://google.com/search?query=JavaScript。

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

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

……但是,如果參數(shù)中包含空格,非拉丁字母等(具體參見下文),參數(shù)就需要被編碼。

因此,有一個(gè) URL 屬性用于解決這個(gè)問題:url.searchParams,URLSearchParams 類型的對象。

它為搜索參數(shù)提供了簡便的方法:

  • ?append(name, value)? —— 按照 ?name? 添加參數(shù),
  • ?delete(name)? —— 按照 ?name? 移除參數(shù),
  • ?get(name)? —— 按照 ?name? 獲取參數(shù),
  • ?getAll(name)? —— 獲取相同 ?name? 的所有參數(shù)(這是可行的,例如 ??user=John&user=Pete?),
  • ?has(name)? —— 按照 ?name? 檢查參數(shù)是否存在,
  • ?set(name, value)? —— set/replace 參數(shù),
  • ?sort()? —— 按 name 對參數(shù)進(jìn)行排序,很少使用,
  • ……并且它是可迭代的,類似于 ?Map?。

包含空格和標(biāo)點(diǎn)符號的參數(shù)的示例:

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

url.searchParams.set('q', 'test me!'); // 添加帶有一個(gè)空格和一個(gè) ! 的參數(shù)

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

url.searchParams.set('tbs', 'qdr:y'); // 添加帶有一個(gè)冒號 : 的參數(shù)

// 參數(shù)會被自動編碼
alert(url); // https://google.com/search?q=test+me%21&tbs=qdr%3Ay

// 遍歷搜索參數(shù)(被解碼)
for(let [name, value] of url.searchParams) {
  alert(`${name}=${value}`); // q=test me!,然后是 tbs=qdr:y
}

編碼(encoding)

RFC3986 標(biāo)準(zhǔn)定義了 URL 中允許哪些字符,不允許哪些字符。

那些不被允許的字符必須被編碼,例如非拉丁字母和空格 —— 用其 UTF-8 代碼代替,前綴為 %,例如 %20(由于歷史原因,空格可以用 + 編碼,但這是一個(gè)例外)。

好消息是 URL 對象會自動處理這些。我們僅需提供未編碼的參數(shù),然后將 URL 轉(zhuǎn)換為字符串:

// 在此示例中使用一些西里爾字符

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 路徑中的 Тест 和 ъ 參數(shù)都被編碼了。

URL 變長了,因?yàn)槊總€(gè)西里爾字母用 UTF-8 編碼的兩個(gè)字節(jié)表示,因此這里有兩個(gè) %.. 實(shí)體(entities)。

編碼字符串

在過去,在出現(xiàn) URL 對象之前,人們使用字符串作為 URL。

而現(xiàn)在,URL 對象通常更方便,但是仍然可以使用字符串。在很多情況下,使用字符串可以使代碼更短。

如果使用字符串,則需要手動編碼/解碼特殊字符。

下面是用于編碼/解碼 URL 的內(nèi)建函數(shù):

  • encodeURI —— 編碼整個(gè) URL。
  • decodeURI —— 解碼為編碼前的狀態(tài)。
  • encodeURIComponent —— 編碼 URL 組件,例如搜索參數(shù),或者 hash,或者 pathname。
  • decodeURIComponent —— 解碼為編碼前的狀態(tài)。

一個(gè)自然的問題:“encodeURIComponent 和 encodeURI 之間有什么區(qū)別?我們什么時(shí)候應(yīng)該使用哪個(gè)?”

如果我們看一個(gè) URL,就容易理解了,它被分解為本文上面圖中所示的組件形式:

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

正如我們所看到的,在 URL 中 :,?,=,&,# 這類字符是被允許的。

……另一方面,對于 URL 的單個(gè)組件,例如一個(gè)搜索參數(shù),則必須對這些字符進(jìn)行編碼,以免破壞 URL 的格式。

  • ?encodeURI? 僅編碼 URL 中完全禁止的字符。
  • ?encodeURIComponent? 也編碼這類字符,此外,還編碼 ?#?,?$?,?&?,?+?,?,?,?/?,?:?,?;?,?=?,??? 和 ?@? 字符。

所以,對于一個(gè) URL 整體,我們可以使用 encodeURI

// 在 url 路徑中使用西里爾字符
let url = encodeURI('http://site.com/привет');

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

……而對于 URL 參數(shù),我們應(yīng)該改用 encodeURIComponent

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

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

將其與 encodeURI 進(jìn)行比較:

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

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

我們可以看到,encodeURI 沒有對 & 進(jìn)行編碼,因?yàn)樗鼘τ谡麄€(gè) URL 來說是合法的字符。

但是,我們應(yīng)該編碼在搜索參數(shù)中的 & 字符,否則,我們將得到 q=Rock&Roll —— 實(shí)際上是 q=Rock 加上某個(gè)晦澀的參數(shù) Roll。不符合預(yù)期。

因此,對于每個(gè)搜索參數(shù),我們應(yīng)該使用 encodeURIComponent,以將其正確地插入到 URL 字符串中。最安全的方式是對 name 和 value 都進(jìn)行編碼,除非我們能夠絕對確保它只包含允許的字符。

?encode*? 與 ?URL? 之間的編碼差異

URL 和 URLSearchParams 基于最新的 URL 規(guī)范:RFC3986,而 encode* 函數(shù)是基于過時(shí)的 RFC2396

它們之間有一些區(qū)別,例如對 IPv6 地址的編碼方式不同:

// IPv6 地址的合法 url
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 (August 1998) 時(shí)代,還不存在 IPv6 url。

這種情況很少見,encode* 函數(shù)在大多數(shù)情況下都能正常工作。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號