正则表达式
本文正则表达式基于javascript
,不同的计算机语言对正则表达式的支持情况以及实现,语法不尽相同,不一定适用于其他语言。
简介
正则表达式
,是一种文本模式(Regular Expression),是对字符串的一种匹配查找规则。 可以方便的在某一文本字符串中,查找、定位、替换符合某种规则的字符串。
比如说,我们想要找出一段文本中的手机号码,文本内容如下:
name:Mark tel:13800138000
name:Jhon tel:13800138888
很明显,在这段文本中,手机号码是以 tel:
开头,这符合一定的规则,这样我们可以通过正则表达式来书写这个规则, 然后去查找匹配:
var text = `name:Mark tel:13800138000
name:Jhon tel:13800138888`
var result = text.match(/tel:(1\d{10})/)
// ["tel:13800138000", "13800138000", index: 0, input: "tel:13800138000", groups: undefined]
var tel = result[1]
// 13800138000
/tel:(1\d{10})/
便是所说的正则表达式。
RegExp
与字面量
在javascript
中,我们可以使用构造函数RegExp
创建正则表达式。
new RegExp(pattern[, flags])
var regExp = new RegExp('\\d', 'g')
也可以通过 字面量的方式:
var regExp = /\d/g
两种创建正则表达式适用的场景有些细微的不同,一般使用new RegExp()
来创建动态的正则表达式,使用字面量创建静态的正则表达式。
正则表达式字面量是提供了对正则表达式的编译,当正则表达式保持不变时,用字面量的方式创建正则表达式可以获得更好的性能。
以下讨论以正则表达式字面量来创建正则表达式:
正则表达式
一般由元字符
和普通字符组成。
元字符
元字符也叫特殊字符,是正则表达式规定的,对符合特定的单一的规则的字符的描述。
字符 | 含义 |
---|---|
\ | 在非特殊字符的前面加反斜杠,表示这个字符是特殊的,不能从字面上解释。比如在\d 描述的不是一个普通的字符d ,而是正则表达式中的数值0-9 。如果在特殊字符前面加反斜杠,这表示将这个字符转义为普通字符,比如 ? 在正则中有其特殊含义,前面加反斜杠?,这可以将其转为普通的? 。 |
^ | 匹配文本开始的位置,如果开启了多行标志,也会匹配换行符后紧跟的位置。 比如 ^a 会匹配abc ,但不会匹配到bac 。 |
$ | 匹配文本结束的位置,如果开启了多行标志,也会匹配换行符前紧跟的位置。 比如 b$ 会匹配acb ,但不会匹配到abc 。 |
* | 匹配前一个表达式0次到多次。 比如, ab* 会匹配到abbbbbbc 中的abbbbbb ,以及acbbbbb 中的a 。 |
+ | 匹配前一个表达式1次到多次。 比如, ab+ 会匹配到abbbbbbc 中的abbbbbb ,但不会匹配acbbbbb 。 |
? | 匹配前一个表达式0次到1次。 比如, ab* 会匹配到abbbbbbc 中的ab ,以及acbbbbb 中的a 。 |
. | 匹配除换行符之外的任何单个字符。 |
x|y | 匹配 x或者y。 |
[xyz] | 表示一个字符的集合。匹配集合中的任意一个字符。可以使用破折号- 来指定一个字符范围。比如, [0-4] 和[01234] ,都可以匹配4567 中的4 。 |
[^xyz] | 表示一个方向字符集合。匹配任意一个不包括在集合中的字符。可以使用破折号- 来指定一个字符范围。比如, [0-4] 和[01234] ,都可以匹配2345 中的5 。 |
{n} | n为一个整数,表示匹配前一个匹配项n次。 比如 a{2} 不会匹配abc 中的a ,但会匹配aaaabc 中的aa 。 |
{m,n} | m,n都是一个整数,匹配前一个匹配项至少发生了m次,最多发生了n次。 当m,n值为0时,这个值被忽略,当n值不写,如 {1,} 表示1次到多次。当m值不写时,如{,1} 表示0次到1次。 |
(x) | 匹配x 并且捕获该匹配项。称为捕获括号,括号中的匹配项也称作子表达式。 |
(?:x) | 匹配x 但不捕获该匹配项。称为非捕获括号。 |
x(?=y) | 匹配x 且当x 后面跟着y 。称为正向肯定查找(正向前瞻)。 |
x(?!y) | 匹配x 且当x 后面不跟着y 。称为正向否定查找(负向前瞻)。 |
[\b] | 匹配一个退格(U+0008)。 |
\b | 匹配一个词的边界。匹配的值的边界并不包含在匹配的内容中。 |
\B | 匹配一个非单词的边界。 |
\d | 匹配一个数字。等价于[0-9] 。 |
\D | 匹配一个非数字。等价于[^0-9] 。 |
\n | 匹配一个换行符 (U+000A)。 |
\r | 匹配一个回车符 (U+000D)。 |
\s | 匹配一个空白字符,包括空格、制表符、换页符和换行符。 等价于 [ \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff] |
\S | 匹配一个非空白字符。 |
\t | 匹配一个水平制表符 (U+0009)。 |
\w | 匹配一个单字字符(字母、数字或者下划线)。 等价于 [A-Za-z0-9_] 。 |
\W | 匹配一个非单字字符。 |
\xhh | 与代码 hh 匹配字符(两个十六进制数字) |
\uhhhh | 与代码 hhhh 匹配字符(四个十六进制数字)。 |
上表在多数文章都会提及,但有一些注意的细节,下面我单独拎出来说说。
[xyz]
匹配集合中的任意一个字符
这个字符集的元素,可以是普通字符,也可以是特殊字符,也可以用破折号-
规定一个字符集范围。
以匹配数字为例,可以写成[0123456789]
,也可以写成[\d]
,也可以写成[0-9]
。
类似于()
等特殊字符,在[]
中有其作用,都特殊字符的作用一致,不能直接当做普通字符来使用,所以我们需要使用反斜杠\
将其转义为普通字符,值得注意的是,上表的特殊字符中,星号*
、小数点.
在[]
中并没有特殊用途,所以不需要做转义处理,当然,即使做了转义,也不会出现问题;而破折号-
在[]
中有其特殊作用,所以作为普通字符使用时,需要转义。?
:匹配前一个表达式0次到1次。
其实这里准确描述来说,匹配前一个表达式,且该表达式 非任何量词*
、+
、?
或{}
,匹配前一个表达式0次到1次。
如果紧跟在 非任何量词*
、+
、?
或{}
的后面,将会使量词变为非贪婪的(匹配尽量少的字符)
贪婪与非贪婪匹配,我们在下文细说。
等价字符
正则表达式中,有不少特殊字符的写法,是等价的,也可以说是简写形式,下表的左右两边,都是等价的。
regExp | regExp |
---|---|
* | {0,} |
+ | {1,} |
? | {0,1} |
\d | [0-9] |
\D | [^0-9] |
\w | [a-zA-Z*] |
\W | [^a-zA-Z*] |
\s | [\f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff] |
贪婪模式与非贪婪
什么是贪婪模式?
贪婪是指正则表达式匹配时,是贪心的,会尽可能的匹配多的字符,主要体现在量词特殊字符
:
// 匹配一个到多个数字
var r = /\d+/
var t1 = '12a'
var t2 = '1234a'
var t3 = 'a12b345'
console.log(t1.match(r)[0]) // 12
console.log(t2.match(r)[0]) // 1234
console.log(t3.match(r)[0]) // 12
非贪婪
,即是让正则表达式匹配尽量少的字符。那么如何改变正则表达式的贪婪模式?
在量词特殊字符后面紧跟使用?
我们说说的量词包括*
, +
, ?
, {m,n}
。那么紧跟了?
,会有什么不同的表现呢?
我们从例子来分析:
var r1 = /<div>.*<\/div>/
var r2 = /<div>.*?<\/div>/
var str = '<div>aaa</div>bbb<div></div>ccc'
变量r1
是贪婪匹配,得到的结果会是什么呢?
console.log(str.match(r1)[0])
// <div>aaa</div>bbb<div></div>
在这段字符串中,有两个</div>
的匹配字符串,正则表达式在遇到第一个</div>
匹配字符项时,同时满足了/.*/
和/<\/div>/
的匹配条件,优先作为/.*/
的匹配值,在遇到第二个时,同样还是优先作为/.*/
的匹配值,直到匹配的字符串str
的结束,没有满足条件的匹配字符串,再把第二个</div>
作为/<\/div>/
的匹配值。最终得到了<div>aaa</div>bbb<div></div>
的匹配结果。
变量r2
这是非贪婪匹配,得到的结果又会有所不同:
console.log(str.match(r1)[0])
// <div>aaa</div>
同样,两个</div>
的匹配字符串,但实际非贪婪匹配模式,在匹配到第一个</div>
,就不会再继续向下匹配字符串了。
也就是说,贪婪匹配是,在满足规则下,尽可能多的匹配更多的字符串,直到字符串结束或没有满足规则的字符串了;非贪婪匹配是,在满足规则下,尽可能少的匹配最少的字符串,一旦得到满足规则的字符串,就不再向下匹配。
x*?
:尽可能少的匹配x
,匹配的结果可以是0个x
;x+?
:尽可能少的匹配x
,但匹配的结果至少有1个x
;x??
:尽可能少的匹配x
, 匹配的结果可以是0个x
,但最多可以有一个x
;x{m,n}?
:尽可能少的匹配x
,但匹配的结果至少有m个x
,最多可以有n个x
;
可能从字面来说,不好理解 x??
, x{m,n}?
,来看一个例子就可以明白了:
var s1 = '<div>aa</div>'
var s2 = '<div>a</div>'
var s3 = '<div></div>'
var r1 = /<div>a??<\/div>/
console.log(r1.test(s1)) // false
console.log(r1.test(s2)) // true
console.log(r1.test(s3)) // true
var s1 = '<div>aaa</div>'
var s2 = '<div>aa</div>'
var s3 = '<div>aaaa</div>'
var r1 = /<div>a{2,3}?<\/div>/
var r2 = /<div>a{2,3}?/
console.log(r1.test(s1)) // true
console.log(r1.test(s2)) // true
console.log(r1.test(s3)) // false
console.log(s1.match(r2)[0]) // <div>aa
console.log(s2.match(r2)[0]) // <div>aa
console.log(s3.match(r2)[0]) // <div>aa
正则表达式标志
标志 | 描述 |
---|---|
g | 全局搜索 |
i | 不区分大小写搜索 |
m | 多行搜索 |
y | 执行“粘性”搜索,匹配从目标字符串的当前位置开始 |
u | Unicode模式。用来正确处理大于 \uFFFF 的Unicode字符 |
m
使用m
标志时,会改变开始(^
)和结束字符($
)的工作模式,变为在多行上匹配,分别匹配每一行的开始和结束,即\n
或\r
分割。
y
使用y
标志时,匹配是从RegExp.lastIndex
指定的位置开始匹配,匹配为真时,会修改 lastIndex
的值到当前匹配字符串后的位置,下次匹配从这个位置开始匹配,如果匹配为假时,不会修改lastIndex
的值。
let reg = /o/y
let str = 'foo'
// lastIndex 为 0,从字符 f 开始匹配
reg.test(str) // false
// 由于结果为 false, lastIndex 还是为 0
reg.test(str) // false
let str2 = 'oof'
// lastIndex 为 0 ,从字符 o 开始匹配
reg.test(str2) // true
// lastIndex 此时修改为 1, 从第二个 o 开始匹配
reg.test(str2) // true
// lastIndex 此时修改为 2
reg.test(str2) // false 此时开始匹配的字符是 f
// lastIndex没有被修改
reg.test(str2) // false
正则表达式中的捕获—— \1,\2,\3... 以及 $1,$2,$3
在上文中我们介绍了 (x)
是匹配 x
并捕获,那么有了捕获就必然可以去使用捕获到的结果, \1,\2,\3...
以及$1,$2,$3...
便是指捕获的结果。
\1, \2, \3, \4, \5, \6, \7, \8, \9
在正则表达式中使用,捕获结果为正则表达式的源模式.
在这个正则表达式中(bc)
被捕获并标记为\1
, (ef)
被捕获并标记为\2
。
let reg = /a(bc)d(ef)/
也可以使用来简化正则表达式
let reg = /a(bc)dbc/
let reg2 = /a(bc)d\1/
let str = 'abcdbc'
reg.test(str) // true
reg2.test(str) // true
$1, $2, $3, $4, $5, $6, $7, $8, $9
是RegExp
的包含括号子表达式的正则表达式静态的只读属性。
let reg = /a(bc)d/
let str = 'abcd'
reg.test(str)
console.log(RegExp.$1) // bc
在 String.replace()
中使用:
let reg = /(\w+)\s(\w+)/
let str = 'apple pear'
str.replace(reg, '$2 $1') // pear apple
RegExp.$1 // apple
RegExp.$2 // pear