文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

如何使用 JavaScript 解析 URL?

2024-12-03 00:06

关注

[[417013]]

在《认识 URI 与 URL》一文中具体介绍了 URI 的格式,要使用 JavaScript 解析 URL 信息,必须先了解 URL 格式是怎样的,让我们先来回顾一下吧。

URL 格式

 

 

完整的 URL 信息包括以下几点:

解析 URL

在回顾了 URL 都包括哪些信息后,现在就先按照前文的 URL 格式人工解析一下 URL 的信息。以本文地址地址为例:

 

  1. http://www.yaohaixiao.com/blog/how-to-parse-url-with-javascript/ 

按照 URL 的格式规范,本文的 URL 地址解析后的信息应该如下:

可以看到,本文地址的 URL 解析后的信息并没有前文提到的完整 URL 信息中那么多。这是因为 URL 信息中有几项信息是可选项信息,本文的示例 URL 地址中都是没有值的。

在通过人工分析的方式分析了一遍,现在就要开始使用 JavaScript 编程解析 URL 信息了。当然,解析 URL 信息的方法很多,本文主要介绍两种解析方法:正则表达式解析 和 URL 构造函数解析。

正则表达式解析

使用 JavaScript 中的正则表达解析 URL 信息应该最常见的方法了,当然这需要具备一定的 JavaScript 正则表达式相关的知识。而使用正则表达式分析 URL 地址其实也并不复杂。

按照前文图片中的 URL 信息的结构,使用括号“()”分组表达符对 URL 中对应的信息进行分组,正则表达式的伪代码大致如下:

 

  1. /^((protocol):)?\/\/((username):(password)@)?(hostname)(:(port))?(pathname)(\\?(search))?(#(hash))?/ 

可以看到,正则表达式也分了 7 组:

完成了大的分组后,接下来要处理的问题就是相对比较容易了,就是用真实的正则表达式将使用英文字母的伪代码内容替换掉。对应完整的 JavaScript 的正则表达式代码如下图:

 

 

可以看到,图中蓝色文字标识的是伪代码中对应的 7 个分组,而灰色文字标识的是最终需要获取的 URL 对应的信息。下面就详细介绍一下各个分组的正则表达式的含义。

1. protocol(协议分组)

 

  1. // ((protocol):)? 
  2. (([^:/?#]+):)? 

 

([^:/?#]+),匹配协议名称(子分组),具体的含义如下:

(([^:/?#]+):)?,匹配协议名称加“:”格式,例如:http: 。当然,在介绍分组伪代码的时候,介绍过了,()? 括号后的 ? 标识整个协议分组是可选的。而之所以将协议分组作为可选的,是应为实际的应用中:

//www.yaohaixiao.com/favicon.ico,这种不带协议名称的 URL 地址也是允许的。

因此,(([^:/?#]+):)? 这段表达式将匹配 2 组数据:http: 和 http,前者是大分组 ()? 匹配的信息,后者则是子分组 ([^:/?#]+) 匹配的信息,也是真正希望解析的 URL 协议信息。不过由于整个协议分组是可选的,因此协议分组的两个分组也可能都匹配不到数据。

2. auth(授权信息分组)

 

  1. // ((username):(password)@)? 
  2. (([^/?#]+):(.+)@)? 

 

([^/?#]+),匹配用户名,由于规则和匹配协议名称一样,在此就不重复了。

(.+),匹配密码。具体含义如下:

(([^/?#]+):(.+)@)?,匹配完整的授权信息。匹配的数据如:yao:Yao1!@。与授权信息一样,最外层的()?表示授权信息也是可选的。

因此,(([^/?#]+):(.+)@)? 整个会匹配 3 组数据:完整的用户授权分组信息、用户名以及密码。由于整个协议分组是可选的,因此授权分组的 3 组信息也可能都匹配不到数据。

3. hostname(服务器地址分组)

 

  1. // (hostname) 
  2. ([^/?#:]*) 

 

([^/?#:]*),匹配服务器地址信息。和协议分组的表达式一样,使用了比较宽松的匹配逻辑。

4. port(端口分组)

 

  1. // (:(port))? 
  2. (:(\d+))? 

 

(\d+),匹配端口号信息。端口号只能是数字类型的数据,对端口号长度的要求是至少有一个。对端口号的长途匹配也没有使用太严苛的长度要求。虽然通常端口号的长度一般是 2 位数字起,但还是建议遵循之前提到的建议,如果不是有具体的精度要求,表达式都可以使用宽泛一些的匹配规则。

(:(\d+))?,匹配完整的端口号分组信息。匹配的格式如:“:80”。

同样的,整个端口号分组匹配的表达式也是可以匹配 2 组数据::80 和 80。当然,端口号分组也是可选的,很大可能配备不到信息。

5. pathname(带层次的文件路径分组)

 

  1. // (pathname) 
  2.  
  3. ([^?#]*) 

 

([^?#]*),匹配带层次的文件路径信息。具体的含义是:

虽然没有使用“()?”的形式表示路径为可选的,但用于路径的长度可以为 0,其实路径也是可选的,也有可能匹配不到数据。

6. search(查询字符串分组)

 

  1. // (\\?(search))? 
  2.  
  3. (\\?([^#]*))? 

 

([^#]*),匹配查询字符串信息。除了“#”(井号)以外的所有字符都可以作为查询字符串信息。[]* 表示可选,因为路径:http://www.yaohxiao.com? 也是允许的。

(\\?([^#]*))?,匹配查询字符串的分组信息。匹配的格式如:?id=23。当然也是可选的。

整个查询字符串分组的表达式(\\?([^#]*))? ,也是可以匹配出 2 组数据。而因为整个分组是可选的,所以查询字符串的分组匹配也很可能匹配不到数据。

7. hash(片段标识符分组)

 

  1. // (#(hash))? 
  2. (#(.*))? 

 

(.*),匹配片段标识。“.”表示任意字符,“*”表示任意长度。即片段表示可以是任意字符,且长度为任意长度的。

(#(.*))?,匹配判断标识分组。匹配的格式如:#1234。看到()?,就知道片段标识符分组是可选的。

整个片段标识符分组的表达式(#(.*))? ,也可以匹配出 2 组数据。当然,也可能什么也匹配不上。

介绍完所有的分组表达式,最后来统计一下最多一共可以匹配多少组数据:2 + 3 + 1 + 2 + 1 + 2 + 2 + 1 = 14。其中,最后一个加1,是匹配的整个 URL 地址。

验证一下使用正则表达式对本文 URL 地址的匹配信息:

 

  1. const URL = 'http://www.yaohaixiao.com/blog/how-to-parse-url-with-javascript/' 
  2. const pattern = /^(([^:/?#]+):)?\/\/(([^/?#]+):(.+)@)?([^/?#:]*)(:(\d+))?([^?#]*)(\\?([^#]*))?(#(.*))?/ 
  3. const matches = URL.match(pattern) 
  4. console.log(matches) 

 

输出的结果为:

 

  1. 0: "http://www.yaohaixiao.com/blog/how-to-parse-url-with-javascript/" 
  2. 1: "http:" 
  3. 2: "http" 
  4. 3: undefined 
  5. 4: undefined 
  6. 5: undefined 
  7. 6: "www.yaohaixiao.com" 
  8. 7: undefined 
  9. 8: undefined 
  10. 9: "/blog/how-to-parse-url-with-javascript/" 
  11. 10: undefined 
  12. 11: undefined 
  13. 12: undefined 
  14. 13: undefined 
  15. groups: undefined 
  16. index: 0 
  17. input: "http://www.yaohaixiao.com/blog/how-to-parse-url-with-javascript/" 

 

正如之前人工分析的一样,使用 match() 方法匹配了 14 个结果。由于示例 URL 地址中很多可选的信息都是没有的,所以匹配结果为 undefined。但这个结果并不是那么一目了然,让我们看看完整的 parseURL() 方法。

完整的 parseURL() 方法

完整的 parseURL() 方法的如下:

 

  1.  
  2. const parseURLWithRegExp = (url = location.href, base) => { 
  3.   const pattern = /^(([^:/?#]+):)?\/\/(([^/?#]+):(.+)@)?([^/?#:]*)(:(\d+))?([^?#]*)(\\?([^#]*))?(#(.*))?/ 
  4.   const getURLSearchParams = (url) => { 
  5.     return (url.match(/([^?=&]+)(=([^&]*))/g) || []).reduce((a, v) => { 
  6.       return ((a[v.slice(0, v.indexOf('='))] = v.slice(v.indexOf('=') + 1)), a) 
  7.     }, {}) 
  8.   } 
  9.   let matches, 
  10.     hostname, 
  11.     port, 
  12.     pathname, 
  13.     search, 
  14.     searchParams 
  15.  
  16.   // url 是为度路径时,忽略 base 
  17.   if (/^(([^:/?#]+):)/.test(url)) { 
  18.     base = '' 
  19.   } 
  20.  
  21.   // 设置了基准 URL 
  22.   if (base) { 
  23.     // 移除 base 最后的斜杠 ‘/’ 
  24.     if (/[/]$/.test(base)) { 
  25.       base = base.replace(/[/]$/, ''
  26.     } 
  27.  
  28.     // 确保 url 开始有斜杠 
  29.     if (!/^[/]/.test(url)) { 
  30.       url = '/' + url 
  31.     } 
  32.  
  33.     // 保证 URL 地址拼接后是一个正确的格式 
  34.     url = base + url 
  35.   } 
  36.  
  37.   matches = url.match(pattern) 
  38.   hostname = matches[6] 
  39.   port = matches[8] || '' 
  40.   pathname = matches[11] || '/' 
  41.   search = matches[10] || '' 
  42.   searchParams = (() => { 
  43.     const params = getURLSearchParams(url) 
  44.  
  45.     return { 
  46.       get (name) { 
  47.         return params[name] || '' 
  48.       } 
  49.     } 
  50.   })() 
  51.  
  52.   return { 
  53.     href: url, 
  54.     origin: (matches[1] ? matches[1] + '//' : '') + hostname, 
  55.     protocol: matches[2] || ''
  56.     username: matches[4] || ''
  57.     password: matches[5] || ''
  58.     hostname, 
  59.     port, 
  60.     host: hostname + port, 
  61.     pathname, 
  62.     search, 
  63.     path: pathname + search, 
  64.     hash: matches[13] || ''
  65.     searchParams 
  66.   } 

 

它返回一个对象,将正则表达式匹配的信息复制给具体的 URL 名称的属性。看看使用 parseURL() 方法解析前面的 URL 地址的结果吧:

 

  1. const URL = 'http://www.yaohaixiao.com/blog/how-to-parse-url-with-javascript/' 
  2. const result = parseURL(URL) 

 

解析后的结果:

 

  1.   hash: undefined, 
  2.   host: "www.yaohaixiao.com"
  3.   hostname: "www.yaohaixiao.com"
  4.   href: "http://www.yaohaixiao.com/blog/how-to-parse-url-with-javascript/"
  5.   orgin: "http://www.yaohaixiao.com"
  6.   password: undefined, 
  7.   path: "/blog/how-to-parse-url-with-javascript/undefined"
  8.   pathname: undefined, 
  9.   port: undefined, 
  10.   protocol: "http"
  11.   search: undefined, 
  12.   username: undefined, 

 

现在解析后的结果是不是一目了然了?当然,使用正则表达式解析 URL 信息肯定不止本文提到的这一种方式,也有比本文中更好,更严谨的匹配规则,但本文中使用的匹配方式相对来说应该是比较易于容易理解和相对兼容性也比较好的一种处理方式。

URL 构造函数解析

除了前文介绍的使用 JavaScript 中的正则表达式解析 URL 信息之外,还可以利用新的 URL 构造函数来解析 URL 地址,并且解析起来更加简单。

URL() 构造函数

URL() 构造函数返回一个新创建的 URL 对象,表示由一组参数定义的 URL。如果给定的基本 URL 或生成的 URL 不是有效的 URL 链接,则会抛出一个 TypeError。

语法如下:

 

  1. new URL(url [, base]); 

调用方法如下:

 

  1. // 直接使用绝对 URL 地址方式调用 
  2. const url = new URL('http://example.com/path/index.html'); 
  3.  
  4. // 使用 path 加 base 地址参数的方式调用 
  5. const url = new URL('/path/index.html''http://example.com'); 

 

URL() 构造函数的接口信息如下:

 

  1. interface URL { 
  2.   href:     USVString; 
  3.   protocol: USVString; 
  4.   username: USVString; 
  5.   password: USVString; 
  6.   host:     USVString; 
  7.   hostname: USVString; 
  8.   port:     USVString; 
  9.   pathname: USVString; 
  10.   search:   USVString; 
  11.   hash:     USVString; 
  12.    
  13.   // 只有 orgin 和 searchParams 是只读,其余的属性都是可修改的 
  14.   readonly origin: USVString; 
  15.   readonly searchParams: URLSearchParams; 
  16.  
  17.   toJSON(): USVString; 

 

所以每个使用 URL() 构造函数创建的实例,都会返回完整 URL 信息了。例如:

 

  1. const url = new URL('http://www.yaohaixiao.com/blog/how-to-parse-url-with-javascript/'); 

返回的数据为:

 

  1.   hash: ""
  2.   host: "www.yaohaixiao.com"
  3.   hostname: "www.yaohaixiao.com"
  4.   href: "http://www.yaohaixiao.com/blog/how-to-parse-url-with-javascript/"
  5.   origin: "http://www.yaohaixiao.com"
  6.   password""
  7.   pathname: "/blog/how-to-parse-url-with-javascript/"
  8.   port: ""
  9.   protocol: "http:"
  10.   search: ""
  11.   searchParams: URLSearchParams {}, 
  12.   username: "" 

 

可以看到,使用 URL() 构造函数返回的数据和前文使用正则表达式解析的数据基本一致,只是这里多了一个 searchParams 对象。

searchParams 对象又是 URLSearchParams 对象的一个实例,用来获取查询字符串中的某个参数的值,用法如下:

 

  1. const url = new URL('http://www.yaohaixiao.com/blog/how-to-parse-url-with-javascript/?id=312'); 
  2. url.searchParams.get('id') // -> 123 

 

URL() 构造函数的功能是不是很强大了。不知道 URL() 构造函数浏览器支持的情况怎么样?

URL() 构造函数的浏览器兼容情况

 

 

在主流浏览器中,除了 IE 浏览器,其余的都基本支持了。基本上可以放心使用 URL() 构造函数来解析 URL 信息。

使用 URL() 构造函数来解析 URL 信息的完整代码如下:

 

  1.  
  2. const parseURLWithURLConstructor = (url= location.href, base) => { 
  3.   const results = new URL(url, base) 
  4.   const protocol = results.protocol.replace(':'''
  5.  
  6.   return { 
  7.     href: url, 
  8.     origin: results.origin, 
  9.     protocol, 
  10.     username: results.username, 
  11.     password: results.password
  12.     hostname: results.hostname, 
  13.     port: results.port, 
  14.     host: results.host, 
  15.     pathname: results.pathname, 
  16.     search: results.search, 
  17.     path: results.pathname + results.search, 
  18.     hash: results.hash, 
  19.     searchParams: results.searchParams 
  20.   } 

 

正则表达式解析 VS URL 构造函数解析

对两种解析 URL 信息的方法进行比较,很明显使用 URL() 构造函数解析的方法操作更加简单,并且提供更多的功能。但与正则表达式解析方法比较,可能唯一不足的就是在 IE 浏览器中无法使用。

其实,只要稍微调整一下,就可以将两种方法结合起来,在支持 URL() 构造函数的浏览器中使用构造函数,不知支持的时候则使用正则表达式解析:

 

  1.  
  2. const parseURL = (url = location.href, base) => { 
  3.   const getURLSearchParams = (url) => { 
  4.     return (url.match(/([^?=&]+)(=([^&]*))/g) || []).reduce((a, v) => { 
  5.       return ((a[v.slice(0, v.indexOf('='))] = v.slice(v.indexOf('=') + 1)), a) 
  6.     }, {}) 
  7.   } 
  8.  
  9.   const parseURLWithRegExp = (url) => { 
  10.     const pattern = /^(([^:/?#]+):)?\/\/(([^/?#]+):(.+)@)?([^/?#:]*)(:(\d+))?([^?#]*)(\\?([^#]*))?(#(.*))?/, 
  11.       matches = url.match(pattern), 
  12.       hostname = matches[6], 
  13.       port = matches[8] || ''
  14.       pathname = matches[11] || '/'
  15.       search = matches[10] || ''
  16.       searchParams = (() => { 
  17.         const params = getURLSearchParams(url) 
  18.  
  19.         return { 
  20.           get (name) { 
  21.             return params[name] || '' 
  22.           } 
  23.         } 
  24.       })() 
  25.  
  26.     return { 
  27.       href: url, 
  28.       origin: (matches[1] ? matches[1] + '//' : '') + hostname, 
  29.       protocol: matches[2] || ''
  30.       username: matches[4] || ''
  31.       password: matches[5] || ''
  32.       hostname, 
  33.       port, 
  34.       host: hostname + port, 
  35.       pathname, 
  36.       search, 
  37.       path: pathname + search, 
  38.       hash: matches[13] || ''
  39.       searchParams 
  40.     } 
  41.   } 
  42.  
  43.   const parseURLWithURLConstructor = (url) => { 
  44.     const results = new URL(url) 
  45.     const protocol = results.protocol.replace(':'''
  46.  
  47.     return { 
  48.       href: url, 
  49.       origin: results.origin, 
  50.       protocol, 
  51.       username: results.username, 
  52.       password: results.password
  53.       hostname: results.hostname, 
  54.       port: results.port, 
  55.       host: results.host, 
  56.       pathname: results.pathname, 
  57.       search: results.search, 
  58.       path: results.pathname + results.search, 
  59.       hash: results.hash, 
  60.       searchParams: results.searchParams 
  61.     } 
  62.   } 
  63.  
  64.   // url 是为度路径时,忽略 base 
  65.   if (/^(([^:/?#]+):)/.test(url)) { 
  66.     base = '' 
  67.   } 
  68.  
  69.   // 设置了基准 URL 
  70.   if (base) { 
  71.     // 移除 base 最后的斜杠 ‘/’ 
  72.     if (/[/]$/.test(base)) { 
  73.       base = base.replace(/[/]$/, ''
  74.     } 
  75.  
  76.     // 确保 url 开始有斜杠 
  77.     if (!/^[/]/.test(url)) { 
  78.       url = '/' + url 
  79.     } 
  80.  
  81.     // 保证 URL 地址拼接后是一个正确的格式 
  82.     url = base + url 
  83.   } 
  84.  
  85.   if (window.ActiveXObject) { 
  86.     return parseURLWithRegExp(url) 
  87.   } else { 
  88.     return parseURLWithURLConstructor(url) 
  89.   } 

 

演示地址:

http://www.yaohaixiao.com/scripts/parseURL/

结束语

随着 Web 技术的不断发展,JavaScript 也在不断地发展,许多新的 API 接口也不断的完善,充分的得到各个主流浏览器的支持。我们在开发过程中就必须不断的关注新技术的更新,找到更加灵活便捷的解决方案来解决开发中的问题。

 

本文仅仅是拿解析 URL 信息作为示例,展示使用不同解决方案的一个实践。如果你有什么更好地解析 URL 信息的方式,也欢迎跟我联系交流。

 

来源:今日头条内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯