本文源自以前面試的時候頻繁要求手寫 url parse ,故針對此種狀況專門寫一文來簡述如何解析 URL ,若是您有更好的解析方法或題型變種歡迎討論javascript
注意,本文僅討論開頭所列出的一種格式,還沒有討論 URL 的更多格式,更多符合規範的格式(如使用相對路徑等的狀況)詳見:tools.ietf.org/html/rfc398…html
首先讓咱們看看一種完整的 URL 是長什麼樣的: <scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>
java
若是這樣太抽象了,那麼咱們舉個例子具體化一下: https://juanni:miao@www.foo.com:8080/file;foo=1;bar=2?test=3&miao=4#test
git
組件 | 描述 | 默認值 | |
---|---|---|---|
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
中github
同時,若是要表示哪些部分是可選的,則能夠表示爲: [scheme:]//[user[:password]@]host[:port][/path][?query][#fragment]
面試
咱們先不考慮組件內部的數據,先獲取每一個組件json
先介紹一個偷懶的方式: 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"
複製代碼
這個正則確實有點難懂,不過相信有一些基礎的話加上下面兩張圖仍是能夠理解:
/** * @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
decodeURIComponent
作不到這點)(至於什麼狀況把 空格 轉換爲 +
,什麼狀況把空格轉換爲 %20
,能夠參考這裏等)array[]
/ obj{}
等形式要求:
JSON.parse
進行解析)/** * @param {string} query 形如 location.search * @returns {object} */
function parseQueryString(query) {
if (!query) {
return {};
}
query = query.replace(/^\?/, '');
const queryArr = query.split('&');
const result = {};
queryArr.forEach(query => {
let [key, value] = query.split('=');
try {
value = decodeURIComponent(value || '').replace(/\+/g, ' ');
key = decodeURIComponent(key || '').replace(/\+/g, ' ');
} catch (e) {
// 非法
console.log(e);
return;
}
const type = getQuertType(key);
switch(type) {
case 'ARRAY':
key = key.replace(/\[\]$/, '')
if (!result[key]) {
result[key] = [value];
} else {
result[key].push(value);
}
break;
case 'JSON':
key = key.replace(/\{\}$/, '')
value = JSON.parse(value);
result.json = value;
break;
default:
result[key] = value;
}
});
return result;
function getQuertType (key) {
if (key.endsWith('[]')) return 'ARRAY';
if (key.endsWith('{}')) return 'JSON';
return 'DEFAULT';
}
}
const testUrl =
'?name=coder&age=20&callback=https%3A%2F%2Fmiaolegemi.com%3Fname%3Dtest&list[]=a&list[]=b&json{}=%7B%22str%22%3A%22abc%22,%22num%22%3A123%7D&illegal=C%9E5%H__a100373__b4'
parseQueryString(testUrl)
複製代碼
固然,這裏還並不嚴謹,沒有考慮到以下問題
+
爲
key
value
Object
最後,這裏推薦一個開源庫:url-parse,對各類狀況處理的比較好,同時這也意味着實現上略複雜,理解便可,面試中更需結合充分理解面試官要求進行解答與擴展