在看博客的时候,不经意间看到了一道这样的面试题:如何使用正则表达式从URL上获取查询参数?html
本身思考了一下,而后尝试用正则表达式来实现这个功能,结果发现本身对正则表达式仍是不够熟悉;因而就顺着这道题目,一步一步的去深挖这里面所涉及到的知识点。es6
之后会不按期更新“我从xxx学到了什么”系列,也是给本身学到的知识进行一个阶段性的总结吧!面试
经过这边文章,你能够学到如下知识点(如下全部代码都是基于JavaScript语言)正则表达式
URL上的查询参数能够直接从window.location.search
获取,获得的字符串就是如下形式数组
?key1=value1&key2=value2
markdown
因此题目就能够转换成:使用正则表达式从以上字符串获取全部的键值对。函数
如下全部的匹配字符串都是 '?name=jim&age=20&hobby=basketball'oop
预期获得的结果学习
1、name=jim
2、age=20
3、hobby=basketball
复制代码
思路:url查询参数部分的字符串格式是固定的:以?
开头,后面接key=val,每组键值对以&
链接url
话很少说,思路有了,就直接上代码
/[?&](.*)=(.*)/g
复制代码
输出的结果
'?name=jim&age=20&hobby=basketball'
复制代码
竟然整个字符串都被匹配上了,问题究竟出在哪里?
想法是匹配以?
或 &
开头的子串,后面跟着key=val,因此写出了 (.*)=(.*)
,这个按理应该能匹配出name=jim这样的字符串。为何在第一个(.*)
就把后面的整个字符串都匹配上了呢?想要的结果是第一个(.*)
匹配到 =
就结束了,也就是key,而后第二个(.*)
继续匹配val。之因此给他们都加上()
就是想在=
前中止匹配,但实际上却没有按照预期获得正确结果。
为何会出现这种状况呢?因而去百度了如下,结果发现,正则表达式默认元字符,量词是采用贪婪模式
贪婪模式会尽量多的匹配知足条件的字符。
以下,都是会匹配最大长度字符串
.*
.+
.{1,}
.{0,}
复制代码
正则表达式元字符,量词默认首先最大匹配字符串,这些量词有:+,*,?,{m,n} 。一开始匹配,就直接匹配到最长字符串。
以下 <h3>abd</h3><h3>bcd</h3>
经过正则表达式:/<h3>.*</h3>/g
进行匹配,获得的结果为:<h3>abd</h3><h3>bcd</h3> 也就是整个字符串都被匹配上了。
知道了正则表达式默认是贪婪模式,那么/[?&].*=.*/g
为何会匹配整个字符串的缘由找到了。
问题就出如今.*
,因为它会尽量多的匹配符合条件的字符串,因此它会把整个字符串都匹配上。
这时就有一个问题:有没有办法让它最小匹配呢?
答案是确定有的,可使用懒惰模式
。
和贪婪模式相反,懒惰模式是进行最小匹配。
很简单,在表示重复字符元字符,后面加多一个?
字符便可。
上面的正则表达式若是想最小长度匹配,则能够这样写:/<h3>.*?</h3>/g 那么匹配出的结果为:
<h3>abd</h3>
<h3>bcd</h3>
复制代码
在一个正则表达式中,若是给子表达式加上()
,就表明这部分是一个分组,整个表达式是第一个分组。
举个栗子
/[?&](.*)=(.*)/g
复制代码
其中(.*)
就是一个分组。
通常分组有两个做用:一、在结果中获取特定的分组匹配结果;二、在表达式中进行引用
在结果中获取特定的分组匹配结果
执行regex.exce(str)
时,获得的结果是一个数组;以下
假如要匹配一个年份的年月日,一个简单的正则能够是这样的
/(\d{4})-(\d{2})-(\d{2})/g
复制代码
匹配字符串1993-01-01
获得的结果以下
[
"1993-01-01",
"1993",
"01",
"01"
]
复制代码
第一个是整个表达式的匹配结果,第二个是第一个分组的匹配结果,依次类推。
那么问题来了,我怎么知道某个分组是第几个?
分组能够经过从左到右计算其开括号来编号。整个表达式始终是第一个分组。
以下表达式 (A)(B(C)) 有四个分组 0 (A)(B(C)) 1 (A) 2 (B(C)) 3 (C) 在执行exec函数时,返回的结果也是按照上面的分组返回结果。 第一个返回结果是0号分组匹配到的字符串;第二个返回结果是1号分组匹配到的字符串;依次类推
在表达式中进行引用
在一些场景中,须要匹配和前面某个子表达式匹配结果同样的结果,好比说,须要匹配重复的单词,那么就可使用分组引用。
一样举个栗子,匹配重复的单词
abc abc
能够经过如下表达式进行匹配
/\b(\w+)\b\s+\1\b/
复制代码
\1
:引用第一个分组匹配到的结果,上面的栗子就是引用(\w+)
获得结果,也就是abc
须要注意的是,引用分组进行匹配,仅仅是引用,它并非真正的分组,因此在结果的数组中是不会有这个引用匹配到的结果。
以上修饰符,若是有不理解的,能够去ES入门教程-正则表达式学习下,这里就再也不赘述了。
不熟悉的能够去菜鸟教程学习下,这里就再也不赘述了。
通过初版的惨痛失败,意识到了要使用懒惰模式进行匹配,因此改进如下获得第二版表达式
[?&]?(.*)?=(.*)?
复制代码
输出结果
1、?name=
2、jim&age=
3、20&hobby=
复制代码
一共匹配到了三个,但结果仍是不是我预期的,
问题在哪里呢?
[?&]?:匹配上的字符串以?或&或都没有开头
.*?:匹配任意字符串,匹配数可使0或者1,也就是说能够是什么都不匹配
.*?=.*?
:第一个.*?
会匹配到=前的字符串,而第二个.*
能够什么都不匹配,由于它是懒惰模式
并且从预期的结果来看,在匹配结果中,并不须要?&
。仅仅是想匹配?或&后面的ke=val。
这时候,后向断言就能够发挥做用了。
这个名字读起来就很别扭,不用去理解它的具体含义,权当是一个名字就行。
语法
(?<=exp)x
复制代码
断言部分在()内,它的做用是匹配知足x的字符串时,校验x前面的字符串是否知足exp,若是知足,则匹配,不然不匹配。
举个具体的栗子
只匹配在#后面的数字
const reg = /(?<=#)\d+/g
const str = '#90abc'
str.match(reg)
// [90]
复制代码
语法
(?<!=exp)x
复制代码
它的做用是匹配知足x的字符串时,校验x前面的字符串是否知足exp,若是不知足,则匹配,不然不匹配。
举个具体栗子
不匹配#后面的数字
const reg = /(?<!#)\d+/g
const str = '#90abc$100edf%102ooo'
str.match(reg)
// [0, 100, 102]
复制代码
语法
x(?=exp)
复制代码
它的做用是匹配知足x的字符串时,校验x后面的字符串是否知足exp,若是知足,则匹配,不然不匹配。
举个具体栗子
只匹配百分号前的数字
const reg = /d+(?=%)/
const str = '90%'
str.match(reg)
// [90]
复制代码
语法
x(?!exp)
复制代码
它的做用是匹配知足x的字符串时,校验x后面的字符串是否知足exp,若是不知足,则匹配,不然匹配。
举个具体栗子
只匹配不在百分号前的数字
const reg = /d+(?!%)/g
const str = '100$'
str.match(reg)
// [100]
复制代码
又多了一件厉害的装备,如今是时候祭出最终版的表达式了
/(?<=[?&])[^&]*/g
复制代码
输出结果
1、name=jim
2、age=20
3、hobby=basketball
复制代码
(?<=[?&])
:后向断言,匹配后面的表达式以后,再次进行验证,若是都知足,则匹配
[^&]*
:匹配不是&的任意字符
?