正则引擎
正则的引擎大致可分为两类:DFA和NFA
- DFA (Deterministic finite automaton) 确定型有穷自动机
- NFA (Non-deterministic finite automaton) 非确定型有穷自动机,大部分都是NFA
DFA引擎不需要进行回溯,所以匹配效率一般情况下要高,但是它并不支持捕获组,于是也就不支持反向引用和$这种形式的引用,也不支持环视(Lookaround)、非贪婪模式等一些NFA引擎特有的特性。
字符和位置
- 如果一个子正则表达式匹配到的是字符,而不是位置,而且会被保存到最终的结果中,那个这个子表达式就是占有字符的,比如/ha/(匹配ha)就是占有字符的;
- 如果一个子正则匹配的是位置,而不是字符,或者匹配到的内容不保存在结果中(其实也可以看做一个位置),那么这个子表达式是零宽度的,比如/read(?=ing)/(匹配reading,但是只将read放入结果中),其中的(?=ing)就是零宽度的,它本质代表一个位置。
修饰符
- i 忽视大小写。
- g 全局匹配。
不启用全局匹配时,match方法返回的数组第一项为第一个匹配内容,后续为各个捕获组
启用全局匹配时会返回所有匹配内容,但不包括捕获组 - m 将
^
和$
变为匹配单行的开始和结尾。(并非有m时才能多行匹配)
- ^ 脱字符,表示文本的开始(有m时则为行初)
- $ 表示文本的结束(有m时则为行末)
- \b 匹配一个单词边界(boundary,匹配一个位置,该位置前后不全是\w能描述的字符且不是中文。通常匹配单词和空格之间的位置,包括开头结尾)
- \B 匹配一个非单词边界
- \d 匹配一个数字字符(digit)
- \D 匹配一个非数字字符
- \s 匹配一个空白字符(space,
空格
和\f\n\r\t\v的超集)- 其中\f是换页符,\n是换行符,\r是回车符,\t是水平制表符,\v是垂直制表符,除空格和\n外均为不可打印字符,因此可以认为匹配[\n| ]
- \S 匹配一个非空白字符
- \w 匹配一个字母或者一个数字或者一个下划线(word)
- \W 匹配一个字母、数字和下划线之外的字符
-
.
可以匹配换行符之外的任意单个字符。
(.|\n)
和[\b\B]
和[\d\D]
和[\s\S]
和[\w\W]
是等价的。
- ? 重复零次或者一次
- + 重复一次或者多次,也就是至少一次
- * 重复零次或者多次,也就是任意次数
- {n} 重复n次
- {n,} 重复n次或者更多次
- {n,m} 重复n次到m次
优先级问题 【正则匹配】优先级从高到低是:
- 转义 \
- 括号(圆括号和方括号)(), (?:), (?=), []
- 字符和位置
- 竖线 |
- 紧贴在量词后的?,开启非贪婪模式
不带问号的限定符也称匹配优先量词,带问号的限定符也称忽略匹配优先量词。
- []中的字符集合只是所有的可选项,最终它只能匹配一个字符。
此时$
&
@
()
等元字符只视为普通字符,不需要转义。 -
^
在字符组中表示取反,不再是文本开始的位置了。 -
-
连字符,匹配范围在它的左边字符和右边字符之间(如[0-9]
)。只有字母和数字可以用连字符。
分歧 :捕获组与非捕获组
[(ab)(cd)]并不会用来匹配字符串“ab”或“cd”,而是匹配a、b、c、d、(、)这6个字符中的任一个,也就是想表达“匹配字符串ab或者cd”这样的需求不能这么做,要这么写ab|cd
()
将其中的字符集合打包成一个集合,供量词操作。且其内容可以被捕获。- 正则内捕获
使用\数字
的形式,从\1
开始,分别对应前面的圆括号捕获的内容(从左向右,从外向内)
。这种捕获的引用也叫反向引用
。
'hello regexA
hello regex
'
.match(/<((A|a)pp)>(hello regex)+<\/\1>\2<\/p>\3<\/p>/);
- 正则外捕获
RegExp是构造正则的构造函数。
RegExp会在我们调用了正则表达式的方法后, 自动将最近一次的结果保存在里面,它的实例属性$数字
会显示对应的引用。
const r = /^(\d{4})-(\d{1,2})-(\d{1,2})$/
r.exec('2019-10-08')
console.log(RegExp.$1)// 2019
console.log(RegExp.$2)// 10
console.log(RegExp.$3)// 08
另外在
replace
方法中也支持引用'hello **regex**'.replace(/\*{2}(.*)\*{2}/, '$1');
// "hello regex"
- 非捕获组
只要在圆括号内最前面加上?:
标识,即不会被捕获,可以节省性能
- 在捕获组内部最前面加上
?
,它就被命名了。使用\k
语法就可以引用已经命名的捕获组。
'hello regex'.match(/<(?[a-zA-Z]+)>.*<\/\k>/);
零宽断言(必须配合圆括号使用) 零宽断言用于匹配一个位置而非字符,因此断言中的内容不会捕获成为结果
- 零宽肯定先行断言 : 圆括号内最左边加上
?=
标识。为紧贴在它前面的规则服务。
'CoffeeScript JavaScript javascript'.match(/\b\w{4}(?=Script\b)\w+/);
// ["JavaScript", index: 13, input: "CoffeeScript JavaScript javascript", groups: undefined]
- 零宽肯定后行断言 : 圆括号内最左边加上
?<=
标识。 - 零宽否定先行断言 : 圆括号内最左边加上
?!
标识。 - 零宽否定后行断言 : 圆括号内最左边加上
?标识。