概述
本文主要通过对正则表达式的语法进行一些简单的介绍,从而让没有接触过或者想学习正则表达式的同学有一个基础的了解,从而能够看懂和编写使用一般的正则表达式。
本文的主要内容为:
- 正则表达式的字符匹配
- 正则表达式的位置匹配
- 正则表达式的括号与捕获组
本文的主要受众是想要学习正则表达式又不知道从何入手的同学。如果你已经使用过正则表达式,可以快速浏览本文,强化自己的记忆即可。
字符匹配
匹配规则
在正则表达式中,分为精确匹配和模糊匹配两种。顾名思义,精确匹配就是匹配特定的字符或者位置;而非精确匹配就是带有一定的范围的匹配。具体示例如下:
const reg1 = /ab/; //精确匹配
const reg2 = /ab+/; //模糊匹配
复制代码
不同的匹配适用于不同的场景,大家根据自己的需求进行选择即可。
字符组
在正则表达式中,我们经常会遇到从N个字符里面选取任意字符进行匹配的需求。这个时候,我们就需要一个字符组。具体示例如下:
const reg = /[abc]/; // 与上面示例完全相同,匹配a或b或者c
复制代码
当匹配的字符多了以后,我们不可能全部都列到字符组里,因此我们可以使用范围表示法。具体示例如下:
const reg = /[a-c]/; // 匹配a或b或c
复制代码
相同类的字符可以用范围,如1-9、A-Z或者a-z。在字符组中,-
是一个特殊字符,如果需要匹配-
,则需要使用\
进行转义。
当然,如果我们是不想匹配N个字符中的任意一个,我们可以用排除字符组的方式来进行匹配。具体示例如下:
const reg = /[^abc]/; // 不匹配a、b、c中任意一个
复制代码
排除字符组中也可以使用范围。
量词
当我们需要匹配单个字符时,我们可以使用上面示例中的方法。但是,如果我们需要匹配单个字符若干次呢?最简单的方法就是将匹配的正则表达式写若干次,但是这样不仅费时费力,还不方便阅读。因此,正则表达式中使用了量词来表示重复匹配N次的情况。
量词含义如下:
{m, }
,至少出现m次。{m, n}
,最少出现m次,最多出现n次(最多出现N次的写法为{0, n},而不是{, n})?
,匹配0或者1次+
,最少匹配1次,与{1, }
等价*
,匹配任意次,与{0, }
等价
了解了上述量词,下面我们来看下这些量词在示例中到底是如何应用的:
const reg1 = /a+/; //至少匹配一次a
const reg2 = /a?b*/; //匹配0或者1次a,再匹配任意次的b
复制代码
现在问题来了,上面示例中的/a+/
这个正则表达式,如果遇到了字符串'aaa'
,那么得到的匹配结果是什么呢?这个就涉及到了我们下一节要介绍的内容。
贪婪匹配与非贪婪匹配
贪婪匹配:所有的量词都会尽可能多的进行匹配,默认值。以/a+/
和'aaa'
为例,匹配的结果是'aaa'
。 非贪婪匹配:所有的两次都会尽可能少的匹配。以/a+?/
和'aaa'
为例,匹配的结果是'a'
。
因为贪婪匹配是默认值,所以当我们写正则表达式时,默认就是贪婪匹配。那么我们应该如何来表示非贪婪匹配呢?具体示例如下:
const reg1 = /a+/; //贪婪匹配
const reg2 = /a+?/; // 非贪婪匹配
复制代码
通过上面的示例我们可以看到,我们只需要在两次后加上一个?
,就表示是一个非贪婪匹配。
注:非贪婪匹配只会向后作用,不会向前作用。即/a+?bb/匹配'aabb'是'aabb',而不是'abb';而/aab+?/则是匹配'aab'。(这个与正则表达式匹配和回溯的原理有关,有兴趣的可以阅读我的下一篇关于正则表达式的博客)
分支逻辑
在一个正则表达式中,我们会遇到做选择的情况。单个元素进行选择时,我们可以使用字符组。但是,如果需要多个元素比如ab
或者cd
进行选择时,这个时候我们就需要分支逻辑。具体示例代码如下:
const reg = /ab|cd/; //表示选择ab或者cd。为什么不是b和c呢?这个我们在下一篇博客——进阶篇中将会讲述操作符优先级问题。
复制代码
位置匹配
正则表达式除了捕获字符,还可以捕获字符串中的位置。所谓的位置,指的就是两个字符之间。比如'ab'
这个字符串,就有3个位置,分别位于a
前面、a
后面b
前面和b
后面。如果我们将位置当成是一个空字符串''
,其实对于位置的匹配也可以归纳到对字符的匹配中。
匹配位置的方式也有不少,我们来看下:
^
,匹配开头,多行模式下匹配行开头,即每行开头都会被匹配。$
,匹配结尾,多行模式下匹配行结尾,即每行结尾都会被匹配。\b
,\w
与\W
之间的位置(\w
表示[0-9A-Za-z],而\W
就是\w
的补集),包括开头结尾(即也包括\w
与^
之间的位置,和\w
与$
之间的位置)\B
,与\b
相反,\w
与\w
之间的位置,和\W
与\W
之间的位置,包括开头结尾(相对的,即包括\W
与^
之间的位置,和\W
与$
之间的位置)(?=p)
,正向肯定断言。p
是一个子模式,匹配要在p
这个模式之前的位置(?!p)
,正向否定断言。与(?=p)
相反,匹配不要在p
这个模式之前的位置
上面说了这么多,下面我们通过一个示例来一下:
const reg1 = /^ab/; //对于字符串'abab'来说,只会匹配到开头的'ab'
const reg2 = /ab$/; //对于字符串'abab'来说,只会匹配到结尾的'ab'
const reg3 = /\b/; //对于字符串'a b'来说,会匹配到'a'前面的位置、'a'和' '之间的位置、' '和'b'之间的位置、'b'后面的位置
const reg4 = /\B/; //对于字符串'aa bb[/来说,会匹配到'a'和'a'之间的位置、'b'和'b'之间的位置、'['后面的位置
const reg5 = /(?=a)/; //对于字符串'bac'来说,会匹配到'a'之前的位置
const reg6 = /(?!a)/; //对于字符串'bac'来说,会匹配到'b'之前的位置、'c'之前的位置以及'c'之后的位置
复制代码
通过上面的例子,大家应该能够理解正则表达式在捕获位置时候所发挥的作用。
ES2018新特性
在ES2018中,增加了反向肯定断言
和反向否定断言
。具体格式如下:
(?<=p)
,反向肯定断言。p
是一个子模式,匹配要在p
模式之后的位置(?,反向否定断言。与
(?<=p)
相反,匹配不要在p
模式之后的位置
我们通过一个具体的示例来看下:
const reg1 = /(?<=a)b/ //对于字符串'abb'来说,只会匹配到'a'和'b'之间的位置。
const reg2 = /(?<=a)b/ //对于字符串'abb'来说,会匹配到'b'和'b'之间的位置。
复制代码
括号与捕获组
在正则表达式中,括号是一个功能非常多的操作符。本章我们将会详细介绍正则表达式中的括号的各种作用。
提高优先级
在正则表达式中,运算符操作也有优先级之分,如下例所示:
const reg1 = /ab|cd/; //匹配'ab'或者'cd'
const reg2 = /a(b|c)d/;//匹配'a'后,匹配一个'b'或者'c',再匹配一个'd'
复制代码
关于正则表达式优先级相关的讨论,我们在此就不做展开了,有兴趣的同学可以阅读我的后一篇关于正则表达式高级进阶的文章。
捕获组与非捕获组
如果我们在正则表达式中,我们需要获取特定的匹配内容,那么我们就要用到捕获组。捕获组通常使用(p)
,其中p
是一个子模式,表示需要捕获的内容。具体使用示例如下:
const reg = /a(bc)d/;
let result = 'abcd'.match(reg); // 得到的result[1]就是第一个捕获组匹配的字符'ab'
复制代码
但是,如果我们在一些需要保证优先级的地方使用了小括号,但是又不想成为捕获组来干扰匹配,我们应该怎么办呢?这个时候我们就需要非捕获组。我们只需要在括号最开始加上一个?
即可。具体使用示例如下:
const reg = /a(?:bc)d/;
let result = 'abcd'.match(reg); // 得到的result没有捕获组
复制代码
反向引用
当我们在正则表达式中需要使用前面捕获组匹配的内容时,我们可以使用反向引用。这在匹配一些成对的字符如'
和"
等时非常有效。具体使用方式如下:
const reg = /(a)b\1/; //匹配字符'aba'
复制代码
这里需要注意的有三点:
- 如果出现括号嵌套的情况,那么从左到右以第一个括号(即左开括号)的顺序为准。
\10
表示的含义为第10个捕获组,而不是第一个捕获组加上一个字符0
。需要表示后者可以用/(\1)0/
。即使是在第三种情况下,转移符优先级仍然高于字符顺序。- 如果在正则表达式中出现的捕获组个数小于使用的捕获组,那么
\
字符就会被当成一个转移符而非反向引用。**注:\2表示对2进行转义的话,不同的浏览器对转义后的结果是不一样的。**下图是Chrome浏览器转义后的结果
总结
通过阅读本文,你已经学到了正则表达式的最基础的语法和使用规则。如果你想提高正则表达式的效率,加快正则表达式的阅读和理解,可以阅读正则表达式系列第二篇文章——正则表达式系列之中级进阶篇。