RegExp简介

正则表达式 (Regular Expression),用于匹配特定模式字符串的模板串

先从最简单的创建和测试开始 (以下内容基于js语言)

RegExp创建

RegExp在js中有两种创建方式,如下所示

1
2
3
4
// 构造函数创建法
let regexp = new RegExp("xxx")
// 字面量创建法
let regexp2 = /xxx/

RegExp测试

正则表达式本身存在test方法,可以用来判断字符串参数和该表达式是否匹配

1
2
let regexp = /hello/
console.log(regexp.test("helloworld"))

上述代码返回的就是true,表示测试字符串中包含了hello

RegExp构成

RegExp由普通字符和特殊字符组成,普通字符即描述自身的字符,而特殊字符赋予RegExp更多的功能,下面主要介绍各种特殊字符的功能

边界

边界符指^符号和$符号

^符号放在RegExp的开头,表示匹配字符串前缀,比如\^hello\表示匹配以hello为开头的字符串

1
2
3
4
5
let regexp = /^hello/
// 结果为true
console.log(regexp.test("helloworld"))
// 结果为false
console.log(regexp.test("hhhelloworld"))

$符号放在RegExp的结尾,表示匹配字符串后缀,比如\world$\表示匹配以world为结尾的字符串,例子和^相似就不举了

^$同时使用的时候表示精准匹配,即只能成功匹配被两个符号包裹的部分

字符选择

[]在RegExp中表示字符选择,被方括号包裹的字符是的关系,在匹配的时候只要满足匹配其中一个即可

比如\[abc]\可以匹配包含abc三个字符中任意一个字符的字符串,下面我写了一个匹配hallo,hbllo或者hcllo的例子

1
2
3
4
5
let regexp = /h[abc]llo/
// 返回false
console.log(regexp.test("helloworld"))
// 返回true
console.log(regexp.test("halloworld"))

在方括号中,可以使用-符号来表示字符组合,当可供选择的字符是连续的时候,就可以用-连接首尾字符来更方便地使用,举个例子会更容易理解

1
2
3
4
5
let regexp = /h[a-z1-9]llo/
// 以下匹配结果均为true
console.log(regexp.test("helloworld"))
console.log(regexp.test("hblloworld"))
console.log(regexp.test("h9lloworld"))

有时候我们还会碰到一些特殊情况,不想匹配的内容是少数,针对这个情况,RegExp还提供了取反,用[^]来表示取反

1
2
3
4
5
6
7
let regexp = /h[^a-z1-9]llo/
// false
console.log(regexp.test("helloworld"))
// false
console.log(regexp.test("hblloworld"))
// true
console.log(regexp.test("hElloworld"))

量词

RegExp中有多种量词符,列表如下

量词 含义
* 重复0次或更多次
+ 重复1次或更多次
? 重复0次或1次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次
1
2
3
4
5
6
7
let regexp = /h{2,5}ello/
// false
console.log(regexp.test("helloworld"))
// true
console.log(regexp.test("hhelloworld"))
// true
console.log(regexp.test("hhhhelloworld"))

元字符

元字符就是有特殊功能的字符,具体见下表,自己意会

元字符 含义
. 单个字符,除了换行和行结束符
\w 字母字符
\W 非字母字符
\d 数字字符
\D 非数字字符
\s 查找空白字符
\S 查找非空白字符
\b 单词边界
\B 非单词边界
\0 NULL字符
\n 换行符
\f 换页符
\r 回车符
\t 查找制表符
\v 查找垂直制表符
\xxx 查找以八进制数 xxxx 规定的字符
\xdd 查找以十六进制数 dd 规定的字符
\uxxxx 查找以十六进制 xxxx规定的 Unicode 字符

转义字符

在正则表达式中所有有特殊含义的字符在匹配本身的时候都需要转义

比如说匹配左括号要用\(

选择

|在RegExp中表示选择,一般和表示优先级的小括号搭配使用,否则会因为出现歧义而导致可匹配范围的扩大

1
2
3
4
5
6
7
let regexp = /(hello|world)/
// true
console.log(regexp.test("hellowrld"))
// false
console.log(regexp.test("hheloorld"))
// true
console.log(regexp.test("hhhhElloworld"))

附加参数

在匹配的时候我们可以对正则表达式附加参数

比如我们对于两种创建正则表达式的方式附加参数i的写法如下

1
2
3
4
// 构造函数创建法
let regexp = new RegExp("xxx", "i")
// 字面量创建法
let regexp2 = /xxx/i

附加这个参数i表示忽略大小写,以下给出附加参数表

附加参数 含义
i (ignoreCase) 忽略大小写匹配
m (multiline) 可以进行多行匹配
g (global) 全局匹配
u (Unicode) 用来正确处理大于\uFFF 的 Unicode 字符
y (sticky) 粘连
s (dotAll) 让’.’能匹配任意字符,包含\n\r

最常使用的就是igs

RegExp匹配

接下来是RegExp最重要的功能,匹配,之前的test测试只能判断一个字符串是否满足正则表达式,而匹配,则是在字符串中捕获需要的内容

匹配有两种方法,match和exec

match方法

match是字符串带有的方法,具体用法如下

1
2
3
let regexp = /hello|world/
let str = "hellowrld"
console.log(str.match(regexp))

会得到这样的一个数组

在匹配中,我们用括号包裹我们需要捕获的内容,比如说我们要捕获aa和bb中间夹杂的字符串,则可以这么写

1
2
3
let regexp = /aa(.*)bb/
let str = "aahellobbwrldhelloworldhello"
console.log(str.match(regexp))

就可以得到这样一个数组,数组的第一个位置存放完整匹配的内容,而第二个位置存放我们希望捕获的内容

当然我们可以有多个要求捕获的内容

1
2
3
let regexp = /aa(.*)bb(.*)/
let str = "aahellobbwrldhelloworldhello"
console.log(str.match(regexp))

得到数组如下

exec方法

exec是正则表达式的方法,使用方法如下

1
2
3
let regexp = /aa(.*)bb(.*)/
let str = "aahellobbwrldhelloworldhello"
console.log(regexp.exec(str))

惰性匹配与贪婪匹配

在不做限定的情况下,正则表达式的匹配是贪婪的,就是在满足条件的情况下匹配最远(长)的字符串,比如说我上边写的那个/aa(.*)bb/,当出现多个bb的时候,会匹配到最后一个bb

1
2
3
4
let regexp = new RegExp("aa(.*)bb","i")
let str = "AA1bb2aa3bb"
// AA1bb2aa3bb
console.log(regexp.exec(str))

如果我们想要匹配到第一个bb,则需要采用惰性匹配规则,在要捕获的内容后边添加?字符,例子见下方代码

1
2
3
4
let regexp = new RegExp("aa(.*?)bb","i")
let str = "AA1bb2aa3bb"
// AA1bb
console.log(regexp.exec(str))

分组

正则表达式中的分组我们在上文就已经提到过,即用()符号框住我们想要捕获的内容(会出现在捕获数组中)或者提升优先级

这里额外补充其两个功能:可以分组引用和分组具名化

分组引用

分组引用允许我们将分组捕获到的内容重复到其它地方,比如下方代码的意思就是在第二个分组匹配任意长度后匹配第一个分组捕获到的内容

1
2
3
let regexp = /aa(.*?)bb(.*\1)/
let str = "aa1bb21aa3bb4"
console.log(regexp.exec(str))

\1表示第一个分组,\2表示第二个分组…

分组具名化

分组具名化的意思就是,给分组取个名字方便我们使用,在分组中用?<名字>格式来具名化

1
2
3
let regexp = /aa(?<第一部分>.*?)bb(<第二部分>.*)/
let str = "aa1bb21aa3bb4"
console.log(regexp.exec(str))

可以得到一个group对象

全局捕获

我们给正则表达式添加附加参数g就表示全局匹配,下面来看看全局匹配的效果

先来看看match方法 (这里要采用惰性匹配,否则第一个直接匹配到了结尾)

1
2
3
let regexp = /aa(.*?)bb/g
let str = "aa1bb21aa3bb4"
console.log(str.match(regexp))

我们发现得到了这样一个数组,数组里面是匹配到的内容

但是失去了捕获功能

再来看看exec

1
2
3
let regexp = /aa(.*?)bb/g
let str = "aa1bb21aa3bb4"
console.log(regexp.exec(str))

捕获功能还是,但是…好像没有全局匹配的样子

exec是属于正则表达式的方法,正则表达式有一个属性lastIndex,表示当前正则下一次匹配的起始索引位置,而全局匹配参数g会在每次exec方法调用后更改lastIndex

比方说,我们多调用几次,并输出这个lastIndex

1
2
3
4
5
6
let regexp = /aa(.*?)bb/g
let str = "aa1bb21aa3bb4"
console.log(regexp.lastIndex, regexp.exec(str))
console.log(regexp.lastIndex, regexp.exec(str))
console.log(regexp.lastIndex, regexp.exec(str))
console.log(regexp.lastIndex, regexp.exec(str))

我们会发现它会一截一截匹配过去,直到找不到为止,然后重头开始匹配

所以我们可以通过在循环中使用exec来完成全局捕获,大概像下面的代码这样

1
2
3
4
5
6
7
var resultArr = []
while(result = reg.exec(str)){
resultArr.push({
info1: result[1],
info2: result[2]
})
}

匹配条件

我们可以为匹配附加条件,来满足一些特殊的要求

只匹配不捕获

在分组开头加上?:表示这个分组只匹配不捕获

1
2
3
let regexp = /aa(?:)bb/
let str = "aa1bb21aa3bb4"
console.log(regexp.exec(str))

正向预查

正向预查符号为?=/aa(?=bb)/表示匹配后面紧跟着bb的aa

比如我们想捕获1b字符串前面一个字符我们可以这么写

1
2
3
4
let regexp = /(.)(?=1b)/
let str = "aa1bb21aa3bb4"
// a
console.log(regexp.exec(str)[1])

反向预查

/(?<=aa)bb/表示匹配前面紧贴着aa的bb

1
2
3
4
let regexp = /(?<=1b)(.)/
let str = "aa1bb21aa3bb4"
// b
console.log(regexp.exec(str)[1])

更多

(?!)类似正向预查,只是除括号内内容外均可匹配,等同于找到所有后面不是xxx的内容

(?<!)同理,类似反向预备

RegExp与字符串处理的结合

split方法

使用方法如下,可以按照正则表达式匹配到的内容分割

1
str.split(regexp)

replace方法

str.replace(reg, str1)可以将匹配到正则表达式的字符串内容替换为str1

1
2
3
let str = '2021-05-16'
str = str.replace(/-/g,'/')
console.log(str)

当然上述的str1还可以换成函数,参数为捕获到的内容,返回值为替换后的内容

1
2
3
4
5
6
let str = "2021-05-16"
str = str.replace(/\d/g, (...args)=>{
return parseInt(args[0])+1
})
// 3132-16-27
console.log(str)

一些例子

先写到这里,空了再补充例子