常见面试题 - URL 解析

栏目: 后端 · 前端 · 发布时间: 6年前

内容简介:本文源自之前面试的时候频繁要求手写 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 用户的密码,和用户名使用 : 分割 E-mail 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 parameterpath 的一部分,因此我们将其归为 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"
复制代码

这个正则确实有点难懂,不过相信有一些基础的话加上下面两张图还是可以理解:

常见面试题 - URL 解析
常见面试题 - URL 解析

解析 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)
复制代码

当然,这里还并不严谨,没有考虑到如下问题

  1. 相同字段如何处理
  2. 没有替换 +
  3. 只有 key
  4. 只有 value
  5. 没有解析相对路径
  6. 更深入的解析 Object

最后,这里推荐一个开源库: url-parse ,对各种情况处理的比较好,同时这也意味着实现上略复杂,理解即可,面试中更需结合充分理解面试官要求进行解答与扩展

参考


以上所述就是小编给大家介绍的《常见面试题 - URL 解析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

腾讯方法

腾讯方法

潘东燕、王晓明 / 机械工业出版社 / 2014-12-11 / 39.00

这是国内第一本深度讲述腾讯产品研发与团队转型的书。本书介绍了腾讯三个不同生命周期的产品的开发过程,包括如何踏足新领域开发新产品;如何救活一个即将半路夭折的产品;如何让一个老产品持续盈利。本书呈现了互联网产品开发时会遇到普遍问题和解决方法,涉及大企业如何内部创业,并迅速组建新的项目团队;如何实现跨部门的合作;在面临新团队和紧急开发任务时如何提高团队沟通效率;在产品研发方面,如何定位产品、如何敏捷开发......一起来看看 《腾讯方法》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具