常见面试题 - URL 解析
本文源自之前面試的時候頻繁要求手寫 url parse ,故針對此種情況專門寫一文來簡述如何解析 URL ,如果您有更好的解析方法或題型變種歡迎討論
注意,本文僅討論開頭所列出的一種格式,尚未討論 URL 的更多格式,更多符合規范的格式(如使用相對路徑等的情況)詳見:tools.ietf.org/html/rfc398…
URL 是啥樣的
首先讓我們看看一種完整的 URL 是長什么樣的: <scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>
如果這樣太抽象了,那么我們舉個例子具體化一下: https://juanni:miao@www.foo.com:8080/file;foo=1;bar=2?test=3&miao=4#test
| scheme | 訪問服務器獲取資源時使用的協議 | 無 | https |
| user | 訪問資源時使用的用戶名 | 無(匿名) | juanni |
| password | 用戶的密碼,和用戶名使用:分割 | miao | |
| host | 資源服務器主機名或IP地址 | 無 | www.foo.com |
| port | 資源服務器監聽的端口,不同的scheme有不同的默認端口(HTTP使用80作為默認端口) | 和scheme有關 | 8080 |
| path | 服務器上的資源路徑。路徑與服務器和scheme有關 | 默認值 | /file |
| params | 在某些scheme下指定輸入參數,是鍵值對。可以有多個,使用;分割,單個內的多個值使用, 分割 | 默認值 | foo=1;bar=2 |
| query | 該組件沒有通用的格式,HTTP中打多使用&來分隔多個query。使用?分隔query和其他部分 | 無 | test=3&miao=4 |
| frag/fragment | 一小片或一部分資源名稱。引用對象時,不會將fragment傳送給服務器,客戶端內部使用。通過#分隔fragment和其余部分 | 無 | test |
由于 path parameter 是 path 的一部分,因此我們將其歸為 path 中
同時,如果要表示哪些部分是可選的,則可以表示為: [scheme:]//[user[:password]@]host[:port][/path][?query][#fragment]
如何獲取每個組件
我們先不考慮組件內部的數據,先獲取每個組件
讓瀏覽器幫我們解析 - URLUtils
先介紹一個偷懶的方式: URLUtils ,可以通過該接口獲取 href 、 hostname 、 port 等屬性。
在瀏覽器環境中,我們的 a 標簽,也就是 HTMLAnchorElement 實現了 URLUtils 中定義的屬性,那么就可以用如下代碼獲得每個組件了
/*** @param {string} url* 利用 URLUtils 簡單解析 URL* @returns {protocol, username, password, hostname, port, pathname, search, hash}*/ function URLParser(url) {const a = document.createElement('a');a.href = url;return {protocol: a.protocol,username: a.username,password: a.password,hostname: a.hostname, // host 可能包括 port, hostname 不包括port: a.port,pathname: a.pathname,search: a.search,hash: a.hash,} } 復制代碼缺點:
- 依賴瀏覽器宿主環境接口
使用 URL 對象
上面使用 a 標簽的方法在 Node 環境中就失效了,但是我們還有其他方法可以讓底層 API 幫我們解析 —— URL
/*** @param {string} url* 利用 URLUtils 簡單解析 URL* @returns {protocol, username, password, hostname, port, pathname, search, hash}*/ function URLParser(url) {const urlObj = new URL(url);return {protocol: urlObj.protocol,username: urlObj.username,password: urlObj.password,hostname: urlObj.hostname,port: urlObj.port,pathname: urlObj.pathname,search: urlObj.search,hash: urlObj.hash,} } 復制代碼老老實實手擼一個
那要是面試官要老老實實的手擼,那也只能對著擼了:
function parseUrl(url) {var pattern = RegExp("^(?:([^/?#]+))?//(?:([^:]*)(?::?(.*))@)?(?:([^/?#:]*):?([0-9]+)?)?([^?#]*)(\\?(?:[^#]*))?(#(?:.*))?");var matches = url.match(pattern) || [];return {protocol: matches[1],username: matches[2],password: matches[3],hostname: matches[4],port: matches[5],pathname: matches[6],search: matches[7],hash: matches[8]}; } parseUrl("https://juanni:miao@www.foo.com:8080/file;foo=1;bar=2?test=3&miao=4#test") // hash: "#test" // hostname: "www.foo.com" // password: "miao" // pathname: "/file;foo=1;bar=2" // port: "8080" // protocol: "https:" // search: "?test=3&miao=4" // username: "juanni" 復制代碼這個正則確實有點難懂,不過相信有一些基礎的話加上下面兩張圖還是可以理解:
解析 search(query) 部分
偷懶使用 URLSearchParams
/*** @param {string} search 類似于 location.search* @returns {object}*/ function getUrlQueyr(search) {const searchObj = {};for (let [key, value] of new URLSearchParams(search)) {searchObj[key] = value;}return searchObj; } 復制代碼優點:
- 不需要手動使用 decodeURIComponent
- 會幫著把 query 上的 + 自動轉換為空格(單獨使用 decodeURIComponent 做不到這點)(至于什么情況把 空格 轉換為 + ,什么情況把空格轉換為 %20,可以參考這里等)
- 不支持如 array[] / obj{} 等形式
再手擼一個(殘缺版)
要求:
- 對于非法字符不予解析
- 對于形如 list[] 的解析成數組
- 對于形如 obj{} 的解析為對象(暫時只需要用 JSON.parse 進行解析)
當然,這里還并不嚴謹,沒有考慮到如下問題
最后,這里推薦一個開源庫:url-parse,對各種情況處理的比較好,同時這也意味著實現上略復雜,理解即可,面試中更需結合充分理解面試官要求進行解答與擴展
參考
- 【讀】這一次,讓我們再深入一點 - URL你是否真的了解?
- path-parameter-syntax
- URLUtils
- URL
- URLSearchParams
總結
以上是生活随笔為你收集整理的常见面试题 - URL 解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 太智能了!国内首批自动驾驶出租车即将在长
- 下一篇: Expected a key while