壹 ? 引
我在 从零开始学正则(四) 一文中讲述了正则匹配的回溯法, 以正则匹配过程引出了正则书写也会存在性能问题, 并阐述了贪婪匹配, 惰性匹配以及分支匹配时与回溯的中中关系. 当然, 对于初学者而言除了能写出正则以外, 能读懂任意一段正则也是非常重要的. 那么本篇文章主要针对正则表达式拆分展开分析, 相信大家在阅读之后再面对各种变态长度的正则时, 都能有理可据, 化繁为简的拆分理解.
说在前面, 正则学习系列文章均为我阅读 老姚《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
肆 ? 总
这一章节内容的内容还算简单, 了解正则的操作符划分对于复杂的正则拆分非常有帮助. 当然在正则熟练后, 我们甚至能一眼看出正则所传递的匹配含义. 我们通过思维导图简单做个整理:
最后留两个思考题, 请写出匹配身份证的正则, 以及尝试分析正则 /^((0{0,2}\d|0?\d{2}|1\d{2}|2[0-4]\d|25[0-5])\.){3}(0{0,2}\d|0?\d{2}|1\d{2}|2[0-4]\d|25[0-5])$/.
那么本文就写到这里, 我要学习第六章节了.
从零开始学正则(五)
来源: http://www.bubuko.com/infodetail-3347177.html