从零开始学正则(五),了解正则解构,让复杂正则化繁为简
壹 ❀ 引
我在 从零开始学正则(四) 一文中讲述了正则匹配的回溯法,以正则匹配过程引出了正则书写也会存在性能问题,并阐述了贪婪匹配,惰性匹配以及分支匹配时与回溯的中中关系。当然,对于初学者而言除了能写出正则以外,能读懂任意一段正则也是非常重要的。那么本篇文章主要针对正则表达式拆分展开分析,相信大家在阅读之后再面对各种变态长度的正则时,都能有理可据,化繁为简的拆分理解。
说在前面,正则学习系列文章均为我阅读 老姚《JavaScript正则迷你书》的读书笔记,文中所有正则图解均使用regulex制作。那么本文开始!
贰 ❀ 正则的解构与操作符
编程语言一般都有操作符(百科),但只要说到操作符就不得不讨论操作符的优先级,因为一堆操作符在一起,系统自己也得知道谁该先执行,谁要后执行。
那么正则中的操作符是什么呢?正则中的操作符体现在正则结构中,而结构又由特殊字符与普通字符构成。
JavaScript中的正则结构大致有这些:字符字面量、字符组、量词、锚、分组、分支、反向引用。也就是前几章节讲过的知识点,我们简单复习一遍:
字符字面量:当我们具体匹配某个字符时所写的正则字段,比如a匹配字段“a”,123匹配字段“123”,\. 匹配小数点等。
字符组:当某个位置的字符可能是多种情况之一时,比如匹配任意一个数字,可以使用字符组[0-9],可简写为\d。除此之外还有反义字符组,比如匹配除了数字之外的任意字符,可以用[^0-9],可简写为\D。
量词:当某个字符需要出现多次时可使用量词加以修饰,比如数字可能出现1次或更多次,可以写成\d{1,},简写便是\d+。
锚:如果说字符字面量以及字符组是匹配的基础,那么锚的功能也是如此,只是前者用于匹配字符,而锚用于匹配位置。比如我们要匹配数字前面的位置可以写(?=\d)。
分组:分组表示一个整体,使用圆括号()表示,比如(ab)+表示字母ab至少会出现一次。
分支:分支使用管道符 | 实现,比如分支 12|34,正则会先尝试使用12进行匹配,如果行不通就会切换成分支34进行匹配,注意分支是惰性匹配。
其中涉及到的操作符以及优先级如下:
操作符描述 | 操作符 | 优先级 |
转义符 | \ | 1 |
圆括号和方括号 | (p)、(?:p)、(?=p)、(?!p)、[p] | 2 |
量词 | {m}、{m,n}、{m,}、?、+、* | 3 |
位置和序列 | ^、$、\元字符、一般字符 | 4 |
管道符 | | | 5 |
知道了这些,我们来从优先级的角度解析正则表达式 /ab?(c|de*)+|fg/
由于不存在转义符,我们通过优先级第二高的括号来拆分正则,首先分组 (c|de*) 是一个整体,而在分组中字母 e 后紧跟了一个量词 *,因此 e* 是一个整体,表示e会出现任意次。最后括号中使用了优先级最低的管道符,所以正则在运行时,一定是先看字母 c ,再看 de*,这里形成了分支。
分析完分组中的内容,再看其它部分,可以得到a、b?、(c|de*)+、f、g这些组合结构,而 ab?(c|de*)+ 和 fg 又使用管道符分割,所以这是两个比较大的分组。当然就算不这么拆分,通过前面的学习解析起来也不是什么难事。我们结合图解来确认下:
叁 ❀ 结构与操作符可能犯错的点
1.分支不使用分组包裹
在匹配整个字符串时,我们通常会添加脱字符^与美元符$,如果我们要匹配字符 abc 或 def 时,容易写成 /^abc|def$/,而此时正则的意思是匹配分支^abc或者def$,它的图解是这样:
而正确的写法应该是利用分组将分支结构进行包裹,所以应该是这样 ,图解为:
2.量词接量词
这个是我前面常犯得错误....量词与量词不能紧接着写。比如我们希望匹配数字1,2,3其一,,且这段数字长度为3的倍数,正则很容易写成 /^[123]{3}+$/ ,然后你会收到浏览器红色问候:
var regex = /^[123]{3}+$/; regex.test(123123);
而正确的写法是 [abc]{3} 是一个整体,所以需要使用分组的括号包裹,完整就是这样:
var regex = /^([123]{3})+$/; regex.test(123123);// true
3.元字符转义问题
正则中的元字符包括 ^、$、.、*、+、?、|、\、/、(、)、[、]、{、}、=、!、:、- 等。在我们需要匹配这些字符时,到底需不需要转义呢?当然全转义肯定也不会有问题:
var string = "^$.*+?|\\/[]{}=!:-,"; var regex = /\^\$\.\*\+\?\|\\\/\[\]\{\}\=\!\:\-\,/; regex.test(string); //true
开发中是否转义还是根据实际情况决定,比如我们真的要匹配字符 “[123]” 时 ,可以在 [ 前添加转义符,表示这不是一个反义字符组。
var string = "[123]"; var regex = /^\[123\]$/; regex.test(string); //true
当然上述正则其实可以简写成 /^\[123]$/ ,因为 [ 前已被转义,后面的 ] 无法成对,所以可以省略转义。
假设我们要匹配 “[^123]” 时可以写成这样,注意 ^ 的转义不可少:
var string = "[^123]"; var regex = /^\[\^123\]$/; regex.test(string); //true
同理,假设要匹配 “{1,3}”,正则我们可以写成 /\{3,5}/ 。
注意有个特例,当匹配圆括号时,前后转义符都得加,因为只写一个会报错。看个例子:
var string = "(123)"; var regex = /^\(123\)$/; regex.test(string); //true
肆 ❀ 总
这一章节内容的内容还算简单,了解正则的操作符划分对于复杂的正则拆分非常有帮助。当然在正则熟练后,我们甚至能一眼看出正则所传递的匹配含义。我们通过思维导图简单做个整理:
那么本文就写到这里,我要学习第六章节了。