字符串基本上都会用到正则表达式,用它来进行字符串匹配、提取、替换非常方便。但是,正则表达式的学习还是有些难度的,贪心匹配、非贪心匹配、捕获子群、非捕获子群等概念,不仅对于初学者来说,对于很多工作了几年的人来说也是如此。如何更好地学习正则表达式?如何快速掌握正则表达式?推荐一个我觉得很好的学习正则表达式的方法:通过AST学习。正则表达式匹配的原理是将模式字符串解析成AST,然后使用这个AST来匹配目标字符串。patternstring中的各种信息解析后会保存在AST中。AST是abstractsyntaxtree,抽象语法树的意思,顾名思义,就是按照语法结构组织起来的树,所以从AST的结构中,可以很容易的知道正则表达式所支持的语法。如何查看正则表达式的AST?可以通过网站astexplorer.net直观查看:将parse语言切换为RegExp,就可以将一个正则表达式的AST可视化。前面说过,AST是一棵按照文法组织起来的树,所以从它的结构上很容易理解各种文法。那我们就从AST的角度来学习各种语法:/abc/从最简单的开始,/abc/这样的正则可以匹配'abc'这个字符串,它的AST是这样的:3个Char,取值是a,b,c分别,类型简单。之后的匹配就是遍历AST,分别匹配这三个字符。我们用exec的api测试:第0个元素为匹配字符串,index为匹配字符串的起始下标。input是输入字符串。再试一下特殊字符:/\d\d\d//\d\d\d/表示匹配三个数字,\d是正则表达式支持的具有特殊含义的元字符(metachar)。我们从AST也可以看出,虽然都是Char,但是类型确实是meta:通过\d元字符可以匹配任意数字:哪个是metachar,哪个是simplechar,通过AST一目了然。/[abc]/Regular支持通过[]指定一组字符,也就是说匹配其中任意一个都可以。通过AST,我们还可以看到它包裹了一层CharacterClass,也就是字符类的意思,即可以匹配它包含的任何字符。测试下确实是这样:/a{1,3}/正则表达式支持指定某个字符重复多少次,形式为{from,to},例如/b{1,3}/表示字符b重复1到3次,/[abc]{1,3}/表示a/b/c字符类重复1到3次。从AST可以看出,这种语法叫做Repetition(重复):他有一个quantifier属性表示一个量词,这里的类型是range,从1到3。Regex也支持一些量词的缩写,比如+表示1到无数次,*表示0到无数次,而?表示0次或1次。是不同类型的量词:有同学可能会问,这里的greedy属性是什么意思?Greedy表示贪心,这个属性表示Repetition是贪心还是非贪心。如果你加一个?量词之后,你会发现greedy变成了false,也就是你切换到非贪婪匹配:greedy和non-greedy是什么意思?让我们看一个例子。默认情况下Repetition的匹配是贪心的,只要满足条件就会继续匹配,所以这里可以匹配acbac。添加一个?量词切换到非贪婪之后,只会匹配第一个:这是贪婪匹配和非贪婪匹配。通过AST,我们可以清楚的知道贪心和非贪心都是针对重复的文法。默认是贪婪匹配,添加一个?在量词切换到非贪婪之后。(aaa)bbb(ccc)正则表达式支持通过()返回子组中的部分匹配字符串。通过AST看一下:对应的AST叫做Group。而且你会发现它有一个捕获属性,默认为true:这是什么意思?这是子组捕获的语法。如果你不想捕获子组,你可以写(?:aaa)。看,capturing变成了false。捕获和非捕获有什么区别?试试看:哦,原来Group的capturing属性代表的是提取还是不提取。我们可以从AST中看到捕获是针对子组的。默认是capture,即提取子组的内容。可以通过?:切换到非捕获,子组的内容将不会被提取。我们已经熟悉使用AST来理解正则语法了,那么让我们来看看难度吧!/bbb(?=ccc)/正则表达式支持(?=xxx)的语法来表达前瞻断言,用于判断某个字符串是否在某个字符串之前。通过AST我们可以看出这个语法叫做Assertion,类型是lookahead,也就是向前看,只匹配前面的意思:这是什么意思?你为什么这样写?和/bbb(ccc)/和/bbb(?:ccc)/有什么区别?我们来试试看:从结果可以看出,/bbb(ccc)/匹配到了ccc的子组,提取了这个子组,因为默认的子组被捕获了。/bbb(?:ccc)/匹配ccc的子组但没有被提取,因为我们通过?:设置了子组不捕获。/bbb(?=ccc)/匹配ccc的子组,不提取子组,说明也是非捕获。它和?:的区别在于ccc不会出现在匹配结果中。这就是lookahead断言的本质:lookahead断言是指某个字符串前面有某个字符串,对应的子组是非捕获的,被断言的字符串不会出现在匹配结果中。如果后面没有那个字符串,它将不匹配:/bbb(?!ccc)/将?=更改为?!之后,意义就会改变。通过AST看一下:虽然lookahead断言还是先断言,但是多了一个负的属性为true。这个意思很明显。本来前面有某串,取反后说明前面不是某串。匹配结果正好相反:只有前一个字符串不是某个字符串,才会匹配。这是一个否定的先行断言。/(?<=aaa)bbb/有前评估和后评估,即只匹配某个字符串。同理,也可以否定:(?<=aaa)对应的AST很容易想到,就是lookbehindassertion:(?
