在JavaScript正则表达式(1)中,我们学习了如何声明一个正则对象以及正则里常用的一些元字符,正则对象的方法和字符串里的正则方法。下面,我们来一起学习入门拓展的JavaScript正则表达式
正则表达式的反向引用###
在正则表达式中,括号能有‘记住’他们包含的子表达式匹配的文本。所以,如果我们运用反向引用
的方法去匹配类似于AA,叠字类型的文本,就简单很多了,当然,反向引用的作用不止如此。具体代码如下:
var reg =/(c.(t))(123)\1\2\3/ig;
var text = '------cat123catt123-------';
text.match(reg);//["cat123catt123"]
\1
是匹配第一个括号里面的,\2
是匹配第一个括号里面嵌套的括号,\3
是匹配最后一个括号。
正则表达式的分组匹配###
分组匹配运用括号的原理和反向引用类似,只是反向引用作用在正则表达式‘内部’,分组匹配在正则表达式‘外部’,语言的形容是苍白无力的,请看下面的具体代码:
var reg =/(c.(t))(123)\1\2\3/ig;
var text = '------cat123catt123-------';
text.match(reg);
console.log(RegExp.$1);//cat
console.log(RegExp.$2);//t
console.log(RegExp.$3);//123
在上述,我们知道可以用()
保留我们想要的数据,并通过一些方法,比如$1
,\1
取得,如果你不想让()
保留,可以使用(?:···)
的方式,让这个括号变为非捕获性括号。这样,正则就不会记录里面的数据了。
正则表达式中的环视(lookaround)###
在了解环视之前,我们一起来思考一个问题,比如有这么一串数字28000000000
,看起来是不是很累?所以一般情况下,我们需要给它加上,
,就像这样28,000,000,000
,但是问题来了,应该如何给这个字符串加上,
呢,我们知道是从右往左,一次3个数字,加一个,
,但是正则表达式都是从左往右解析的。聪明的你一定想到了,我们以左边至少有一个数字,右边是3的倍数个数字的规则去匹配,不就行了吗?所以,我们尝试写下了如下的正则表达式:
var reg = /([0-9]+)([0-9][0-9][0-9])+/g;
var text = '28000000000'
while(text!== text.replace(reg,'$1,$2')){
text = text.replace(reg,'$1,$2')
};
console.log(text)//28,000,000,000
很显然,我们得到了我们想要的结果,但是是不是显得不太优雅?至少我认为是的,分析一下代码,我们发现,正则匹配是只要匹配过去之后就不会回头再去匹配(下一次匹配开始的地方是上一次匹配结束的地方),所以要通过一个while
循环,一直遍历到无法匹配为止。
所以,在这个时候,环视的概念就显得尤为重要,它的概念是什么呢?
环视是不匹配任何字符,只匹配文本中的特定位置的一种结构。与^
,\b
,$
有点类似。既然是一种结构,那么理所应当的,它在匹配的时候并不会占用字符。
不占用字符是一种怎么样的概念呢?具体代码如下:
var reg = /moon(?=burn)/;
var reg2 = /moon(?:burn)/;
var text = 'moonburn';
var text1 = 'moonbUrn'
console.log(text.match(reg));//["moon", index: 0, input: "moonburn"]
console.log(text.match(reg2));//["moonburn", index: 0, input: "moonburn"]
console.log(text1.match(reg));//null
通过上述代码,应该很容易就可以发现,环视的作用,使用环视的方式就是(?=···)
,而moon(?=burn)
的意思就是说,匹配一个moon
然后后面是burn
的字符串,moon(?:burn)
的意思其实和moon(?=burn)
理解起来是差不多的,但是还是有一个很大的区别,就是上面提出的,用环视的方式进行匹配,匹配的字符本身不会被算为占用的字符,也就是说,用moon(?=burn)
,匹配到n
的时候,遇到了环视,然后判断后续是否符合环视的规则,如果符合,返回moon
,如果不符合,返回null
,而moon(?:burn)
的意思是,匹配到n
之后,继续向后匹配b
,u
,r
,n
,如果都匹配成功,返回moonburn
(因为burn也是匹配项),失败,返回null
。这就是环视最大的优点匹配的时候并不会占用字符。
环视的种类####
当然,为了需要,环视也不仅仅只有(?=···)
一种,还有以下的:
类型 | 正则表达式 | 匹配成功的条件 |
---|---|---|
肯定顺序环视 | (?=···) | 子表达式能够匹配右侧文本 |
否定顺序环视 | (?!···) | 子表达式不能匹配右侧文本 |
环视大家差不多已经有所了解了,那么回到一开始的那个例子,通过环视。如何更优雅的给一串数字加上,
呢?具体代码如下:
var reg =/([0-9])(?=([0-9][0-9][0-9])+(?![0-9]))/g;
var text = '28000000000';
text.replace(reg,'$1,');//28,000,000,000
是不是通过环视就把while
循环去掉了,变得清爽了很多?确实,环视的作用确实蛮大的,希望大家能够理解,熟练运用并掌握。
字符组简记法
在上述例子中,我们用[0-9]
来匹配一个数字,其实在JavaScript中(很多其他语言也是一样的),用\d
来表示匹配一个数字,所以,上面的代码可以改为:
var reg =/(\d)(?=(\d\d\d)+(?!\d))/g;
var text = '28000000000';
text.replace(reg,'$1,');//28,000,000,000
是不是更加清爽了?在我看来,[0-9]
与\d
是完全等价的,只是\d
的写法更简单,明了一些。这种就叫做字符组简记法,当然,不仅仅只有\d
一个,更多的如下:
字符 | 匹配对象 | 注释 |
---|---|---|
\d |
数字 | 等价于[0-9] |
\D |
非数字字符 | 等价于[^\d] |
\w |
单词中的字符 | 等价于[a-zA-Z0-9_] (至少在JavaScript是这样) |
\W |
非单词字符 | 等价于[^\w] |
\s |
空白字符 | 等价于[ \f\n\r\t\v] |
\S |
非空白字符 | 等价于[^\s] |
JavaScript中正则的引擎
首先,要知道,正则的引擎种类繁多,但是大致可以分为2个大类,一种是NFA
,一种是DFA
。JavaScript用的是NFA
引擎。通过简单的代码判断:
var reg = /nfa|nfa not/g;
var text = 'nfa not';
text.match(reg)//["nfa"];
可以得知,JavaScript是传统型的NFA
,不是POSIX NFA
。
在了解不同引擎的差异之前,我们可以先来了解一下它们的共同点。以下共同点适用于所有引擎:
- 优先选择最左端(最开头)的位置匹配。
- 标准的匹配量词
*
,+
,?
和{m.n}
是匹配优先的。
下面 开始讨论第一条规则,优先选择最左端(最开头)的位置匹配:
举个例子,例如以下情况:
var reg = /cat/g;
var text = '123catt45678cat'
text.search(reg)//3
在text
中,满足reg
的位置有2个,分别是3
,12
,因为优先选择左端的位置,所以匹配了3
位置的cat
。
再来一个例子:
var reg = /cat|dog|monkey/g;
var text = '123monkeydogcatt45678cat'
text.search(reg)//3
这个例子很简单,告诉我们,正则表达式的匹配是所有的匹配项都会尝试一遍,哪怕monkey
项在正则的最后面,也会最先匹配。
下面开始讨论第二条规则,标准的匹配量词*
,+
,?
和{m.n}
是匹配优先的。
关于上述的匹配量词,都是匹配优先的,匹配优先是个什么意思呢?比如,当我们用了+
来进行匹配的时候,当匹配到一个满足条件之后,他会继续往下匹配,一直匹配到无法匹配为止。如果我们看到匹配出来的结果不是最大匹配项,那么一定是因为最大匹配项的情况下无法满足后续的匹配规则,就减少匹配项从而满足后续项,这种全部匹配的过程,就是匹配优先的过程。举个例子:
var reg = /[0-9]+0/g;
var text = '12340567890123';
text.match(reg);//["12340567890"]
这个是匹配成功了,一开始的[0-9]+
就把1234567890123
全部匹配完了(匹配优先,就是这么厉害!不服不行!),但是匹配完了之后,发现无法满足后面的匹配项0
,没有办法,只能退回一个,看看能不能满足,于是[0-9]+
的匹配项变成了``123456789012,发现还是不行,重复上述过程······一直退回到
123456789,后面匹配到
0,满足了!搞定收工!返回了
["12340567890"],细心的你,一定是发现了,其实在前面的
12340也是满足这个正则的,并且是排在了更加靠左的地方,但是却被忽略了,为什么?因为有关匹配量词的引擎内部的计算方法就是这样的,或者说因为**匹配优先**,而选择了后面的符合条件的
1234567890`。
为了巩固大家对匹配优先的理解,我准备趁热打铁,再来一个可能大家会混淆的例子(希望是我多虑了):
var reg = /[0-9]+([0-9]+)/g;
var text = '12340567890123';
text.match(reg);
RegExp.$1;//是多少呢?
先允许我卖个关子,先来分析一波,想一下匹配优先的原则,一开始的[0-9]+
把text
全部匹配完了,然后慢慢退回,退回一个3
给([0-9]+)
之后,满足了,完成走人......可能有人会想,后面一个也是+
啊为什么才给它一个数字呢,没办法,先来先服务嘛,勉强给你一个满足就不错了,你还要几个?还要啥自行车?所以RegExp.$1
的值是3
。
答对了吗?如果答对了,那你应该对匹配优先了解透彻了,如果没有答对或者很犹豫,那请你在看看前文吧。因为万丈高楼平地起,很多复杂的正则表达式都是从这个开始的。基础尤为重要。
JavaScript 正则表达式(1)
JavaScript 正则表达式(2)
JavaScript 正则表达式(3)
JavaScript 正则表达式(4)