内容简介:本文源自之前面试的时候频繁要求手写 url parse ,故针对此种情况专门写一文来简述如何解析 URL ,如果您有更好的解析方法或题型变种欢迎讨论注意,本文仅讨论开头所列出的一种格式,尚未讨论 URL 的更多格式,更多符合规范的格式(如使用相对路径等的情况)详见:首先让我们看看一种完整的 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{}
等形式
再手撸一个(残缺版)
要求:
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 ,对各种情况处理的比较好,同时这也意味着实现上略复杂,理解即可,面试中更需结合充分理解面试官要求进行解答与扩展
参考
以上所述就是小编给大家介绍的《常见面试题 - URL 解析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。