需求

实现对网页地址链接的匹配;
一些典型的网页链接如下:

如上所示,需要匹配下面的6个:

协议

协议头一般有http、https、ftp等这里写一下匹配的方式:

  1. let protocol = /(?:ht|f)tp(?:s)?(?=:\/\/)/
  2. protocol.exec('http://1234')
  3. // ["http", index: 0, input: "http://1234", groups: undefined]

解释:

  • 使用非捕获组/(?:ht|f)/,只对值判断,但是不将结果,也就是(ht|p)看作是一个捕获组;
  • 使用正向零宽断言(?=:\/\/),匹配后面的://,只有有此符号才算匹配。

    正则表达式之捕获组和非捕获组

域名

对于域名,一般来说有两种情况,一个事数字的ip域名,一种是字符域名。

IP地址的长度为32位(共有2^32个IP地址),分为4段,每段8位 用十进制数字表示,每段数字范围为0~255,段与段之间用句点隔开。 https://www.jianshu.com/p/82886d77440c

  1. 对于ip域名:

    1. 数字在0~255之间,/^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/
    2. 总共4段: /^(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:(?:\.)(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3,3}$/
    3. 解释一下,上面的匹配数字的方法避免了001这样的匹配,然后匹配四段后面的{3,3}则是保证只能是4段。

      DNS规定,域名中的标号都由英文字母和数字组成,每一个标号不超过63个字符,也不区分大小写字母。标号中除连字符(-)外不能使用其他的标点符号。级别最低的域名写在最左边,而级别最高的域名写在最右边。由多个标号组成的完整域名总共不超过255个字符。 https://developer.aliyun.com/article/297853

  2. 对于字符的域名:

    1. 按照规则可以写出一个域名: ‘test-12.admin.abandon.work’
    2. 匹配它: /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)+([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$/
  3. 将两中匹配的规则合并起来:
    1. 首先匹配ip,然后再匹配域名
    2. 将二者合并起来
    3. 参考: Regular expression to match DNS hostname or IP Address?
    4. /^(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:(?:\.)(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3,3}$|^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)+([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$/
      1. /^(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:(?:\.)(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3,3}$|^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)+([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$/.exec("www.abandon.work");
      2. // ["www.abandon.work", undefined, "abandon.", "abandon", "work", index: 0, input: "www.abandon.work", groups: undefined]
      在线演示请看这里
      这里还可以对捕获组进行一点优化,聪明的小伙伴你看看是怎么搞的。

      端口号

      其实最困难的部分已经过去啦,下面对端口号的匹配就很简单了,匹配的是冒号后面的数字。
      1. /(?<=\:)[0-9]+$/.exec('http://www.abandon.wor:8080');
      2. // ["8080", index: 23, input: "http://www.abandon.wor:8080", groups: undefined]
  • 需要注意的是这里使用了一个正向后行断言,用来判断端口号前面的冒号,这样匹配的捕获组里面就没有冒号啦。
  • 当然 不是所有的域名都会把端口号暴露在外面,为了保护自己的底裤,很多的页面是没有端口号显示的,因此在最后拼接正则表达式的时候需要注意这一点。

    路由

    对于路由,如法炮制,注意路由是由一个/开始的,并且这个斜杠的后面一个字符一定不是/: ```javascript /(\/[0-9a-z#.]+)+|(\/)/.exec(‘abandon.work/‘);

// [“/“, undefined, “/“, index: 12, input: “abandon.work/“, groups: undefined]

/(\/[0-9a-z#.]+)+|(\/)/.exec(‘abandon.work/admin/#/articles/id’);

// [“/admin/#/articles/id”, “/id”, index: 12, input: “abandon.work/admin/#/articles/id”, groups: undefined]

  1. - 这里我认为还有优化的空间,可以把每一个都匹配出来才是最方便的。**<TODO>**
  2. <a name="94Z0U"></a>
  3. ### query参数
  4. query参数从?开始,中间是&链接,键值对保持key=value形态。<br />上代码:
  5. ```javascript
  6. /(\?[0-9a-z&=]+)/.exec('abandon.work/api?tab=10&date=10-11');
  7. // ["?tab=10&date=10", "?tab=10&date=10", index: 16, input: "abandon.work/api?tab=10&date=10-11", groups: undefined]

要是可以直接匹配出键值对就好了。或者匹配出key=value的形式。

  1. var url = 'name=ooo&age=10';
  2. var reg = /([^&=]+)=?([^&]*)/g;
  3. // 每执行一次就吐出一堆key&value
  4. reg.exec(url)
  5. ["name=ooo", "name", "ooo", index: 0, input: "name=ooo&age=10", groups: undefined]
  6. reg.exec(url)
  7. ["age=10", "age", "10", index: 9, input: "name=ooo&age=10", groups: undefined]

根据上面的代码可以写出一个很常见的查询参数的方法

  1. const getParams = (url ,name) => {
  2. let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
  3. let r = url.match(reg);
  4. if (r != null) {
  5. return decodeURIComponent(r[2]);
  6. };
  7. return null;
  8. }
  9. let url = 'https://cn.bing.com/search?q=ip%E5%9C%B0%E5%9D%80+%E8%A7%84%E5%88%99&qs=n&form=QBRE&sp=-1&pq=ip%E5%9C%B0%E5%9D%80+%E8%A7%84%E5%88%99&sc=1-7&sk=&cvid=FC57C982563B4F188629B32CAE541761';
  10. getParams(url, "pq")
  11. // "ip地址+规则"

另外, 正则表达式获取url中的所有参数和值

页面hash

页面的hash匹配的是文章阅读的锚点,注意不要和hash路由搞混了。

  1. /#([0-9a-zA-Z\-]+)/.exec('abandon.work/api#introduction')
  2. // ["#introduction", "introduction", index: 16, input: "abandon.work/api#introduction", groups: undefined]

总结

合并上面的正则的时候要注意有时候url中没有某些参数也是正确的。

  1. const http = /(?:ht|f)tp(?:s)?(?=:\/\/)/;
  2. const domain = /^(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:(?:\.)(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3,3}$|^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)+([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$/;
  3. const port = /((?<=\:)[0-9]+)$/;
  4. const route = /(\/[0-9a-z#.]+)+|(\/)/;
  5. const query = /(\?[0-9a-z&=]+)/;
  6. const hash = /#([0-9a-zA-Z\-]+)/;
  7. const url = "https://www.abandon.work:6001/#/blog?startDate=1&endDate=10&promote=false#test";
  8. const regex = /^*$/i; <TODO>
  9. regex.exec(url);
  10. regex.test(url)
  1. regex.exec(url)
  2. [
  3. "http://www.abandon.work:6001/#/blog?startDate=1&endDate=10&promote=false#test",
  4. "http://",
  5. "www.abandon.work",
  6. ":6001",
  7. "/#/blog",
  8. "?startDate=1&endDate=10&promote=false",
  9. "#test",
  10. index: 0,
  11. input: "http://www.abandon.work:6001/blog?startDate=1&endDate=10&promote=false#test",
  12. groups: undefined
  13. ]

大功告成!希望之后你能用正则解决更多的问题。

Ref

正则表达式之捕获组和非捕获组
菜鸟教程 Regex