之前实习工作或者刷题的时候有时碰到需要用到正则表达式地方,都是在网上直接找的,昨天系统的学习了一下,在此做个记录以及用自己的方式讲出来,巩固一下,或许也能帮到你。
参考链接:https://github.com/ziishaned/learn-regex/blob/master/translations/README-cn.md
首先正则表达式是用来匹配字符串的,在JS中封装有RegExp方法供我们使用,我们只需要编写匹配的规则,就能用来匹配或者对特定要求的字符串做出判断了。
例如可以搭配字符串的replace方法来将匹配到的字符串替换成想要的字符串,也可以用regexp.test(str)方法来判断字符串是否符合我们编写的正则表达式。
基本匹配
那么最基本的正则表达式就是一段直白的字符串咯,例如/test/,就会匹配字符串(this is the test)中的test。
元字符
但正则的精髓在于利用元字符来代替普通的字符,以此在冗长复杂的字符串中匹配到我们想要的内容。下表是一些元字符,他们都有着特殊的含义:
元字符 | 描述 |
---|---|
. | 句号匹配任意单个字符除了换行符。 |
* | 匹配>=0个重复的在*号之前的字符。 |
+ | 匹配>=1个重复的在*号之前的字符。 |
? | 标记?之前的字符为可选。 |
[ ] | 字符种类。匹配方括号内的任意字符。 |
[^ ] | 否定的字符种类。匹配除了方括号里的任意字符 |
{num1, num2} | 匹配num1<=x<=num2个花括号之前的字符或字符集。 |
( ) | 字符集,匹配括号内所有字符 |
^ | 从开始开始匹配(也就是必须以什么开头) |
$ | 从末尾开始匹配(以什么为结束) |
| | 或运算符,匹配符号前或后的字符 |
\ | 转义字符,用于匹配一些保留的字符 [ ] ( ) { } . * + ? ^ $ \ |
那么有了这些元字符,我们就可以编写正则表达式了,例如编写一个匹配手机号码的表达式,必须以13、18、15开头的11位号码。
const reg = new RegExp(/^1[385][0-9]{9}$/)
const str1 = '13226154267'
const str2 = '14226154267'
console.log(reg.test(str1), reg.test(str2)) // true false
上述例子使用到了^$[]{}这4个元字符,^$代表了字符串只能是特定格式的手机号,如果不是以手机号开头并且结尾的就不匹配,例如在手机号前面或者后面多加一位数字都不行,因为我们限定了长度。
1[385]代表了第一位一定是1,而第二位只能在385里面选择,那么前两位确定了,后面只剩下9位,所以继续给定0-9的字符集[0-9],并且限定匹配个数为9个{9},这样子一个符合题意的正则表达式就写出来了。
当然也不止这种写法,如果能理解上面的元字符,相信这种写法你也能快速理解:
const reg = new RegExp(/^1(3|8|5)[0-9]{9}$/)
或者
const reg = new RegExp(/^(13|18|15)[0-9]{9}$/)
简写字符集
可能这时你就发现上面写法有些冗余,因为一个数字需要写[0-9],那如果还要包含大小写字母或者一些符号,那匹配一个字符将会如下面一样很长:
const reg = new RegExp(/[0-9a-zA-Z_@]/)
所以接下来是要学一些简写字符集,如下表:
简写 | 描述 |
---|---|
. | 除换行符外的所有字符 |
\w | 匹配所有字母数字,等同于 [a-zA-Z0-9_] |
\W | 匹配所有非字母数字,即符号,等同于: [^\w] |
\d | 匹配数字: [0-9] |
\D | 匹配非数字: [^\d] |
\s | 匹配所有空格字符,等同于: [\t\n\f\r\p{Z}] |
\S | 匹配所有非空格字符: [^\s] |
\f | 匹配一个换页符 |
\n | 匹配一个换行符 |
\r | 匹配一个回车符 |
\t | 匹配一个制表符 |
\v | 匹配一个垂直制表符 |
\p | 匹配 CR/LF(等同于 \r\n),用来匹配 DOS 行终止符 |
\b | 表示单词边界 |
\B | 表示非单词边界,应理解为(非单词)边界,而不是非(单词边界),它仍然匹配的是边界 |
有了简写字符集我们也可以再优化下我们的表达式啦
const reg = new RegExp(/^1[385]\d{9}$/)
学到这里已经能解决大部分需要用到正则表达式的场景了,但还没完,因为上述我们匹配的都是针对需要匹配的部分(我在说什么),如果你想要匹配包含(或者不包含)有特定前缀或者后缀的字符串,但不想把这些前后缀包含在里面,就需要用到零宽度断言(前后预查)了。
零宽度断言(前后预查)
?=… 正先行断言
正先行断言,表示第一部分表达式之后必须跟着 ?=…定义的表达式。
?!.. 负先行断言
正先行断言,用于筛选所有匹配结果,筛选条件为 其后不跟随着断言中定义的格式。
听着可能有点懵,但我举个例子你就明白了。例如在一个字符串数组中,我需要匹配压缩包的文件名,那么压缩包的文件名格式是 由字母、数字和下滑线组成的名字(不能以数字开头),再加上后缀.tar、.rar、.zip。
const filename = ['demo', '1reg.zip', 're0.txt', 'study1.tar', 'setup.exe']
const reg = new RegExp(/^[a-zA-Z_]\w*(?=.(tar|rar|zip)$)/)
for(const name of filename) {
console.log(reg.exec(name)) // 获取匹配的字符串
}
//null
//null
//null
//['study1', 'tar', index: 0, input: 'study1.tar', groups: undefined]0: "study1"1: "tar"groups: undefinedindex: 0input: "study1.tar"length: 2[[Prototype]]: Array(0)...]
//null
上面的例子就是使用了正先行断言,应该能理解了吧,那么负先行断言和下面的正后发断言和负后发断言也是同样的道理。另外值得注意的是,这些断言需要写在()里面。
?<= … 正后发断言
正后发断言 记作(?<=…) 用于筛选所有匹配结果,筛选条件为 其前跟随着断言中定义的格式。
?<!.. 负后发断言
负后发断言 记作 (?<!..) 用于筛选所有匹配结果,筛选条件为 其前不跟随着断言中定义的格式。
再来一个例子,例如我要筛选出中国大陆地区的手机号码,那么应该是+86开头的,但是我只取号码的主体部分,而不要前缀。知道了规则之后就可以用正则表达式来编写了。
const reg = new RegExp(/(?<=^\+86)1[385]\d{9}$/) //注意加号需要转义
标志
在上面所举的例子中,我们都是只验证一整个字符串是否是匹配的,那么如果我们要匹配字符串中所有符合正则表达式的字串呢?那就需要用到标志了,以下是几种正则表达式的标志,也是模式修正符:
标志 | 描述 |
---|---|
i | 忽略大小写。 |
g | 全局搜索。 |
m | 多行修饰符:锚点元字符 ^ $ 工作范围在每行的起始。 |
这个应该挺好理解的,我就不举例子了。
贪婪匹配与惰性匹配
正则表达式默认采用贪婪匹配模式,在该模式下意味着会匹配尽可能长的子串。我们可以使用 ? 将贪婪匹配模式转化为惰性匹配模式。
const str = 'The fat cat sat on the mat.'
const reg1 = new RegExp(/(.*at)/)
const reg2 = new RegExp(/(.*?at)/)
console.log(reg1.exec(str))
console.log(reg2.exec(str))
//['The fat cat sat on the mat', 'The fat cat sat on the mat', index: 0, input: 'The fat cat sat on the mat.', groups: undefined]
//['The fat', 'The fat', index: 0, input: 'The fat cat sat on the mat.', groups: undefined]
关于正则表达式的内容基本就差不多了,干看着好像挺容易的,但一上手可能就不知道怎么使用或者记不住了,主要还是得多练习!