Javascript Web前端之路让前端飞

JavaScript 正则表达式(2)

2017-06-04  本文已影响91人  moonburn

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之后,继续向后匹配burn,如果都匹配成功,返回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

在了解不同引擎的差异之前,我们可以先来了解一下它们的共同点。以下共同点适用于所有引擎:

  1. 优先选择最左端(最开头)的位置匹配。
  1. 标准的匹配量词*,+,?{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)

上一篇下一篇

猜你喜欢

热点阅读