正则表达式必知必会

正则表达式是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些匹配某个模式的文本。正则表达式的语法中的关键概念有:元字符、量词、字符边界、选择表达式、分组与引用等。

元字符

代码 说明
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束

转义字符

\是转义字符,其后面的字符会代表不同的意思,转义字符主要有三个作用:

  1. 是为了匹配不方便显示的特殊字符,比如换行,tab符号等
  2. 正则中预先定义了一些代表特殊意义的字符,比如\w等
  3. 在正则中某些字符有特殊含义(比如下面说到的),转义字符可以让其显示自身的含义

下面是常用转义字符列表:

转义字符 含义
\n 匹配换行符
\r 匹配回车符
\t 匹配制表符,也就是tab键
\v 匹配垂直制表符
\x20 20是2位16进制数字,代表对应的字符
\u002B 002B是4位16进制数字,代表对应的字符
\u002B 002B是4位16进制数字,代表对应的字符
\w 匹配任何一个字母或者数字或者下划线
\W 匹配任何一个字母或者数字或者下划线以外的字符
\s 匹配空白字符,如空格,tab等
\S 匹配非空白字符
\d 匹配数字字符,0~9
\D 匹配非数字字符
\b 匹配单词的边界
\B 匹配非单词边界
\ 匹配\本身

[]

有时我们需要匹配一类字符,字符集可以实现这个功能,字符集的语法用[]分隔,下面的代码能够匹配a或b或c:

1
[abc]

如果要表示字符很多,可以使用-表示一个范围内的字符,下面两个功能相同:

1
2
[0123456789]
[0-9]

在前面添加^,可表示非的意思,下面的代码能够匹配abc之外的任意字符:

1
[^abc]

其实正则还内置了一些字符集,在上面的转义字符有提到,下面给出内置字符集对应的自定义字符集:

字符 说明
. 匹配除了换行符(\n)以外的任意一个字符 = [^\n]
\w [0-9a-zA-Z_]
\W [^0-9a-zA-Z_]
\s [ \t\n\v]
\S [^ \t\n\v]
\d [0-9]
\D [^0-9]

量词

如果我们有三个苹果,我们可以说自己有个3个苹果,也可以说有一个苹果,一个苹果,一个苹果,每种语言都有量词的概念。如果需要匹配多次某个字符,正则也提供了量词的功能,正则中的量词有多个,如?+*{n}{m,n}{m,}

量词 释义
{n} 匹配n次,比如a{2},匹配aa
{m, n} 匹配m-n次,优先匹配n次,比如a{1,3},可以匹配aaa、aa、a
{m,} 匹配m-∞次,优先匹配∞次,比如a{1,},可以匹配aaaa…
? 匹配0次或1次,优先匹配1次,相当于{0,1}
+ 匹配1-n次,优先匹配n次,相当于{1,}
* 匹配0-n次,优先匹配n次,相当于{0,}

正则默认和人心一样是贪婪的,也就是常说的贪婪模式,凡是表示范围的量词,都优先匹配上限而不是下限:

1
a{1, 3} // 匹配字符串'aaa'的话,会匹配aaa而不是a

有时候这不是我们想要的结果,可以在量词后面加上?,就可以开启非贪婪模式

1
a{1, 3}? // 匹配字符串'aaa'的话,会匹配a而不是aaa

字符边界

有时我们会有边界的匹配要求,比如以xxx开头,以xxx结尾。^[]外表示匹配开头的意思:

1
^abc // 可以匹配abc,但是不能匹配aabc

$表示匹配结尾的意思:

1
abc$ // 可以匹配abc,但是不能匹配abcc

上面提到的\b表示单词的边界:

1
abc\b // 可以匹配 abc ,但是不能匹配 abcc

选择表达式

有时我们想匹配x或者y,如果x和y是单个字符,可以使用字符集,[abc]可以匹配a或b或c,如果x和y是多个字符,字符集就无能为力了,此时就要用到分组。正则中用|来表示分组,a|b表示匹配a或者b的意思:

1
123|456|789 // 匹配 123 或 456 或 789

分组

分组是正则中非常强大的一个功能,可以让上面提到的量词作用于一组字符,而非单个字符,分组的语法是圆括号包裹(xxx):

1
(abc){2} // 匹配abcabc

分组不能放在[]中,分组中还可以使用选择表达式:

1
(123|456){2} // 匹配 123123、456456、123456、456123

和分组相关的概念还有一个捕获分组和非捕获分组,分组默认都是捕获的,在分组的(后面添加?:可以让分组变为非捕获分组,非捕获分组可以提高性能和简化逻辑:

1
2
'123'.match(/(?:123)/) // 返回 ['123']
'123'.match(/(123)/) // 返回 ['123', '123']

引用

和分组相关的另一个概念是引用,比如在匹配html标签时,通常希望<xxx></xxx>后面的xxx能够和前面保持一致。引用的语法是\数字,数字代表引用前面第几个捕获分组,注意非捕获分组不能被引用:

1
<([a-z]+)><\/\1> // 可以匹配 `<span></span>` 或 `<div></div>`等

预搜索

如果你想匹配xxx前不能是yyy,或者xxx后不能是yyy,那就要用到预搜索,js只支持正向预搜索,也就是xxx后面必须是yyy,或者xxx后面不能是yyy:

1
2
1(?=2) // 可以匹配12,不能匹配22
1(?!2) // 可有匹配22,不能匹配12

(?:pattern)

匹配 pattern 但不获取匹配结果,这是一个非获取匹配,不进行存储供以后使用。这在使用 “或” 字符 (|) 来组合一个模式的各个部分是很有用。例如, 'industr(?:y|ies) 就是一个比 'industry|industries' 更简略的表达式。

(?=pattern)

正向预查,在任何匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如, 'Windows (?=95|98|NT|2000)' 能匹配 "Windows 2000" 中的 "Windows" ,但不能匹配 "Windows 3.1" 中的 “Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。

(?!pattern)

负向预查,在任 何不匹配的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如'Windows (?!95|98|NT|2000)' 能匹配 "Windows 3.1" 中的 "Windows",但不能匹配 "Windows 2000" 中的 "Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。

修饰符

默认正则是区分大小写,这可能并不是我们想要的,正则提供了修饰符的功能,修复的语法如下:

1
/xxx/gi // 最后面的g和i就是两个修饰符

g 正则遇到第一个匹配的字符就会结束,加上全局修复符,可以让其匹配到结束
i 正则默认是区分大小写的,i可以忽略大小写
m 正则默认情况下,^和$只能匹配字符串的开始和结尾,m修饰符可以让^和$匹配行首和行尾

1
2
3
4
5
/jing$/ // 能够匹配 'yanhaijing,不能匹配 'yanhaijing\n'
/jing$/m // 能够匹配 'yanhaijing, 能够匹配 'yanhaijing\n'

/^jing/ // 能够匹配 'jing',不能匹配 '\njing'
/^jing/m // 能够匹配 'jing',能够匹配 '\njing'