RegExp 语法
元字符
元字符(Meta-Character) 指那些在正则表达式中具有特殊意义的专用字符,可以用来规定其前导字符(即位于元字符前面的字符)在目标对象中的出现模式。
元字符 | 名称 | 匹配对象 |
---|---|---|
. |
点号 | 单个任意字符(除回车 \r 、换行 \n 、行分隔符 \u2028 和段分隔符 \u2029 外) |
[] |
字符组 | 列出的单个任意字符 |
[^] |
排除型字符组 | 未列出的单个任意字符 |
? |
问号 | 匹配 0 次或 1 次 |
* |
星号 | 匹配 0 次或多次 |
+ |
加号 | 匹配 1 次或多次 |
{min,max} |
区间量词 | 匹配至少 min 次,最多 max 次 |
^ |
脱字符 | 行的起始位置 |
$ |
美元符 | 行的结束位置 |
` | ` | 竖线 |
() |
括号 | 限制多选结构的范围,标注量词作用的元素,为反向引用捕获文本 |
\1,\2... |
反向引用 | 匹配之前的第一、第二...组括号内的表达式匹配的文本 |
字符类别
字符组简记
用 [0-9]
、[a-z]
等字符组,可以很方便地表示数字字符和小写字母字符。对于这类常用字符组,正则表达式提供了更简单的记法,这就是字符组简记(Shorthands)。
常见的字符组简记有 \d
、\w
、\s
,其中:
d
表示(Digit)数字w
表示(Word)单词s
表示(Space)空白
正则表达式也提供了对应排除型字符组的简记法:\D
、\W
、\S
。字母完全相同,只是改为大写,这些简记法匹配的字符互补。
字符 | 描述 |
---|---|
\d |
数字,等同于 [0-9] |
\D |
非数字,等同于 [^0-9] |
\s |
空白字符,等同于 [\f\n\r\t\u000B\u0020\u00A0\u2028\u2029] |
\S |
非空白字符,等同于 [^\f\n\r\t\u000B\u0020\u00A0\u2028\u2029] |
\w |
字母、数字、下划线,等同于 [0-9A-Za-z_] |
\W |
非字母、数字、下划线,等同于 [^0-9A-Za-z_] |
任意字符
字符 | 描述 |
---|---|
. |
表示除回车 (\r) 、换行 (\n) 、行分隔符 (\u2028) 和段分隔符 (\u2029) 以外的任意字符。 |
⚠️ 注意:一般认为点号可以代表任意字符,其实并不是
妥善的利用互补属性,可以得到一些巧妙的效果。比如,
[\s\S]
、[\w\W]
、[\d\D]
都可以表示任意字符。
匹配任意字符
/./.test('\r');
// false
/[\s\S]/.test('\r');
// true
转义字符
转义字符(Escape) 表示为反斜线 \
加字符的形式,共有以下 3 种情况。
字符 | 描述 |
---|---|
\ + 元字符 |
匹配元字符 |
\ + ] 或 \ + } |
右方括号和右花括号无需转义 |
\ + 非元字符 |
表示一些不能打印的特殊字符 |
\ + 除上述其他字符 |
默认情况匹配此字符 |
<br />
因为元字符有特殊的含义,所以无法直接匹配。如果要匹配它们本身,则需要在它们前面加上反斜杠 \
。
/1+1/.test('1+1');
// false
/1\+1/.test('1+1');
// true
/\*/.test('*');
// true
/*/.test('*');
// 报错
但实际上,并非 14 个元字符都需要转义,右方括号 ]
和右花括号 }
不需要转义
/]/.test(']');
// true
/\]/.test(']');
// true
/\}/.test('}');
// true
/}/.test('}');
// true
\
加非元字符,表示一些不能打印的特殊字符。
<br />
字符 | 描述 |
---|---|
\0 |
NUL 字符 \u0000 |
[\b] |
匹配退格符 \u0008 ,不要与 \b 混淆 |
\t |
制表符 \u0009 |
\n |
换行符 \u000A |
\v |
垂直制表符 \u000B |
\f |
换页符 \u000C |
\r |
回车符 \u000D |
\xnn |
由十六进制数 nn 指定的拉丁字符 |
\uxxxx |
由十六进制数 xxxx 指定的 Unicode 字符( \u4e00 - \u9fa5 代表中文) |
\cX |
控制字符 ^X ,表示 ctrl-[X] ,其中的 X 是 A-Z 之中任一个英文字母,用来匹配控制字符 |
\
加任意其他字符,默认情况就是匹配此字符,也就是说,反斜线 (\)
被忽略了。
/\x/.test('x');
// true
/\y/.test('y');
// true
/\z/.test('z');
// true
双重转义
由于 RegExp
构造函数的参数是字符串,所以某些情况下,需要对字符进行 双重转义。
字符 \
在正则表达式字符串中通常被转义为 \\
。
const reg1 = /\.at/;
// 等价于
const reg2 = new RegExp('\\.at');
const reg3 = /name\/age/;
// 等价于
const reg4 = new RegExp('name\\/age');
const reg5 = /\w\\hello\\123/;
// 等价于
const reg6 = new RegExp('\\w\\\\hello\\\\123');
字符集合
字符集合(Character Sets),有的编译成字符类或字符集。简单而言,就是指用方括号表示的一组字符,它匹配若干字符之一。
字符 | 描述 |
---|---|
[xyz] |
一个字符集合,也叫字符组。匹配集合中任意一个字符。可以使用 - 指定一个范围。 |
[^xyz] |
一个反义或补充字符集,也叫反义字符组。匹配任意不包括括号内的字符。可以使用 - 指定一个范围。 |
<br />
// 匹配 0-9 这 10 个数字之一
const regexp = /[0123456789]/;
regexp.test('1');
// true
regexp.test('a');
// false
字符组中的字符排列顺序并不影响字符组的功能,出现重复字符也不会影响。
以下三个表达式都是相等的。
const regexp1 = /[0123456789]/;
const regexp2 = /[9876543210] /;
const regexp3 = /[1234567890123456789]/;
范围
正则表达式通过连字符 (-)
提供了范围表示法,可以简化字符组
const regexp1 = /[0123456789]/;
// 等价于
const regexp2 = /[0-9]/;
const regexp3 = /[abcdefghijklmnopqrstuvwxyz]/;
// 等价于
const regexp4 = /[a-z]/;
连字符 (-)
表示的范围是根据 ASCII 编码的码值来确定的,码值小的在前,码值大的在后。
所以 [0-9]
是合法的,而 [9-0]
会报错。
//匹配 0-9 这 10 个数字之一
const regexp1 = /[0-9]/;
regexp1.test('1');
// true
const regexp2 = /[9-0]/;
// 报错
regexp2.test('1');
在字符组中可以同时并列多个 -
范围。
const regexp1 = /[0-9a-zA-Z]/;
// 匹配数字、大写字母和小写字母
const regexp2 = /[0-9a-fA-F]/;
// 匹配数字,大、小写形式的a-f,用来验证十六进制字符
const regexp3 = /[0-9a-fA-F]/.test('d');
// true
const regexp4 = /[0-9a-fA-F]/.test('x');
// false
只有在字符组内部,连字符 -
才是元字符,表示一个范围,否则它就只能匹配普通的连字符号。
如果连字符出现在字符组的开头或末尾,它表示的也是普通的连字符号,而不是一个范围。
// 匹配中划线
/-/.test('-');
// true
/[-]/.test('-');
// true
// 匹配0-9的数字或中划线
/[0-9]/.test('-');
// false
/[0-9-]/.test('-');
// true
/[0-9\-]/.test('-');
// true
/[-0-9]/.test('-');
// true
/[\-0-9]/.test('-');
// true
排除
字符组的另一个类型是 排除型字符组,在左方括号后紧跟一个脱字符 ^
表示,表示在当前位置匹配一个没有列出的字符。
所以 [^0-9]
表示 0-9 以外的字符。
// 匹配第一个是数字字符,第二个不是数字字符的字符串
/[0-9][^0-9]/.test('1e');
// true
/[0-9][^0-9]/.test('q2');
// false
在字符组内部,脱字符 ^
表示排除,而在字符组外部,脱字符 ^
表示一个行锚点。
^
符号是元字符,在字符组中只要 ^
符号不挨着左方括号就可以表示其本身含义,不转义也可以。
// 匹配 abc 和 ^ 符号
/[a-c^]/.test('^');
// true
/[a-c\^]/.test('^');
// true
/[\^a-c]/.test('^');
// true
在字符组中,只有 ^
、 -
、[
、]
这 4 个字符可能被当做元字符,其他有元字符功能的字符都只表示其本身。
/[[1]]/.test('[');
// false
/[[1]]/.test(']');
// false
/[\1]/.test('\\');
// false
/[^^]/.test('^');
// false
/[1-2]/.test('-');
// false
/[\[1\]]/.test('[');
// true
/[\[1\]]/.test(']');
// true
/[\\]/.test('\\');
// true
/[^]/.test('^');
// true
/[1-2\-]/.test('-');
// true
数量词
正则表达式提供了量词,用来设定某个模式出现的次数。
字符 | 描述 |
---|---|
x* |
相当于 x{0,} (匹配任意多次) |
x+ |
相当于 x{1,} (匹配至少一次) |
x? |
相当于 x{0,1} (不匹配或匹配一次) |
x*? 或 x+? |
相当于 * 和 + 字符,然而匹配的是最小可能匹配 |
x(?=y) |
只有当 x 后面紧跟着 y 时,才匹配 x 。(了解详情请看 环视) |
x(?!y) |
只有当 x 后面不是紧跟着 y 时,才匹配 x 。(了解详情请看 环视) |
x|y (这里是没有 \ 的) |
匹配 x 或 y |
x{n} |
匹配 n 次(n 为正整数) |
x{n,m} |
匹配至少 n 次,最多 m 次(n 和 m 为正整数) |
x{n,} |
匹配至少 n 次(n 为正整数) |
<br />
邮政编码
// 表示邮政编码 6 位数字
const regexp = /\d{6}/;
美国英语和英国英语有些词的写法不一样,如果 traveler
和 traveller
,favor
和 favour
,color
和 colour
。
// 同时匹配美国英语和英国英语单词
const regexp1 = /travell?er/;
const regexp2 = /favou?r/;
const regexp3 = /colou?r/;
协议名有 HTTP 和 HTTPS 两种:
const regexp1 = /https?/;
选择
竖线 |
在正则表达式中表示或关系的选择,以竖线 |
分隔开的多个子表达式也叫选择分支或选择项。在一个选择结构中,选择分支的数目没有限制。
在选择结构中,竖线 |
用来分隔选择项,而括号 ()
用来规定整个选择结构的范围。如果没有出现括号,则将整个表达式视为一个选择结构。
选择项的尝试匹配次序是从左到右,直到发现了匹配项,如果某个选择项匹配就忽略右侧其他选择项,如果所有子选择项都不匹配,则整个选择结构匹配失败。
/12|23|34/.exec('1');
// null
/12|23|34/.exec('12');
// ['12']
/12|23|34/.exec('23');
// ['23']
/12|23|34/.exec('2334');
// ['23']
IP 地址一般由 3 个点号和 4 段数字组成,每段数字都在 0-255 之间。
- 0-199:
[01]?\d\d?
- 200-249:
2[0-4]\d
- 250-255:
25[0-5]
IP 地址:
const ipRegExp = /((2[0-4]\d|25[0-5]|[0-1]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[0-1]?\d\d?)/;
ipRegExp.test('1.1.1.1');
// true
ipRegExp.test('1.1.1');
// false
ipRegExp.test('256.1.1.1');
// false
类似地,时间匹配也需要分段处理:
// 月(1-12)
0?\d|1[0-2]
// 日(1-31)
0?\d|[12]\d|3[01]
// 小时(0-24)
0?\d|1\d|2[0-4]
// 分钟(0-60)
0?\d|[1-5]\d|60
手机号一般是 11 位,前 3 位是号段,后 8 位一般没有限制。而且,在手机开头很可能有 0 或+86。
- 开头:
(0|\+86)?
- 前 3 位:
13\d|14[579]|15[0-35-9]|17[0135-8]|18\d
- 后 8 位:
\d{8}
const phone = /(0|\+86)?(13\d|14[579]|15[0-35-9]|17[0135-8]|18\d)\d{8}/;
phone.test('13453250661');
// true
phone.test('1913250661');
// false
phone.test('1345325061');
// false
在选择结构中,应该尽量避免选择分支中存在重复匹配,因为这样会大大增加回溯的计算量
// 错误示范 🙅♂️
const regexp = /a|[ab][0-9]|\w/;
贪婪模式
默认情况下,量词都是贪婪模式(Greedy quantifier),即匹配到下一个字符不满足匹配规则为止。
// exec 方法以数组的形式返回匹配结果
/a+/.exec('aaa');
// ['aaa']
懒惰模式
懒惰模式(Lazy quantifier) 和贪婪模式相对应,在量词后加问号 ?
表示,表示尽可能少的匹配,一旦条件满足就再不往下匹配。
符号 | 释义 |
---|---|
{n}? |
匹配 n 次 |
{n,m}? |
匹配至少 n 次,最多 m 次 |
{n,}? |
匹配至少 n 次 |
?? |
相当于{0,1} |
*? |
相当于{0,} |
+? |
相当于{1,} |
<br />
示例:
/a+?/.exec('aaa');
// ['a']
匹配 <script></script>
之间的代码看上去很容易
const regexp = /<script>[\s\S]*<\/script>/;
regexp.exec('<script>alert("1");</script>');
// ["<script>alert("1");</script>"]
但如果多次出现 script
标签,就会出问题
const regexp = /<script>[\s\S]*<\/script>/;
regexp.exec('<script>alert("1");</script><br><script>alert("2");</script>');
// ["<script>alert("1");</script><br><script>alert("2");</script>"]
它把无用的 <br>
标签也匹配出来了,此时就需要使用懒惰模式
const regexp = /<script>[\s\S]*?<\/script>/;
regexp.exec('<script>alert("1");</script><br><script>alert("2");</script>');
// ["<script>alert("1");</script>"]
在 JavaScript 中,/* */
是注释的一种形式,在文档中可能出现多次,这时就必须使用懒惰模式
const regexp = /\/\*[\s\S]*?\*\//;
regexp.exec('/*abc*/<br>/*123*/');
// ["/*abc*/"]
分组与反向引用
分组
量词控制之前元素的出现次数,而这个元素可能是一个字符,也可能是一个字符组,也可以是一个表达式。
如果把一个表达式用括号包围起来,这个元素就是括号里的表达式,被称为 子表达式。
示例 1:如果希望字符串 ab
重复出现 2 次,应该写为 (ab){2}
,而如果写为 ab{2}
,则 {2}
只限定 b
。
/(ab){2}/.test('abab');
// true
/(ab){2}/.test('abb');
// false
/ab{2}/.test('abab');
// false
/ab{2}/.test('abb');
// true
示例 2:身份证长度有 15 位和 18 位两种,如果只匹配长度,可能会想当然地写成 \d{15,18}
,实际上这是错误的,因为它包括 15、16、17、18 这四种长度。
// 正确写法
var idCard = /\d{15}(\d{3})?/;
示例 3:Email 地址以 @
分隔成两段,之前的部分是用户名,之后的部分是主机名。
用户名允许出现数字、字母和下划线,长度一般在 1-64 个字符之间,则正则可表示为 /\w{1,64}/
主机名一般表现为 a.b.···.c
,其中 c
为主域名,其他为级数不定的子域名,则正则可表示为 /([-a-zA-z0-9]{1,63}\.)+[-a-zA-Z0-9]{1,63}/
所以 email 地址的正则表达式如下:
const email = /\w{1,64}@([-a-zA-z0-9]{1,63}\.)+[-a-zA-Z0-9]{1,63}/;
email.test('q@qq.com');
// true
email.test('q@qq');
// false
email.test('q@a.qq.com');
// true
捕获
括号不仅可以对元素进行分组,还会保存每个分组匹配的文本,等到匹配完成后,引用捕获的内容。因为捕获了文本,这种功能叫 捕获分组。
比如,要匹配诸如 2016-06-23
这样的日期字符串
const regexp = /(\d{4})-(\d{2})-(\d{2})/;
与以往不同的是,年、月、日这三个数值被括号括起来了,从左到右为第 1 个括号、第 2 个括号和第 3 个括号,分别代表第 1、2、3 个捕获组。
JavaScript 有 9 个用于存储捕获组的构造函数属性。
RegExp.$1
、RegExp.$2
、RegExp.$3
到 RegExp.$9
分别用于存储第一、第二第九个匹配的捕获组。
在调用 exec()
或 test()
方法时,这些属性会被自动填充。
/(\d{4})-(\d{2})-(\d{2})/.test('2016-06-23');
// true
console.log(RegExp.$1);
// '2016'
console.log(RegExp.$2);
// '06'
console.log(RegExp.$3);
// '23'
console.log(RegExp.$4);
// ''
而 exec()
方法是专门为捕获组而设计的,返回的数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串。
/(\d{4})-(\d{2})-(\d{2})/.exec('2016-06-23');
// ["2016-06-23", "2016", "06", "23"]
捕获分组捕获的文本,不仅可以 用于数据提取,也可以 用于替换。
replace()
方法就是用于进行数据替换的,该方法接收两个参数,第一个参数为待查找的内容,而第二个参数为替换的内容。
'2000-01-01'.replace(/-/g, '.');
// 2000.01.01
在 replace()
方法中也可以引用分组,形式是 $num
, num
是对应分组的编号。
把 2000-01-01
的形式变成 01-01-2000
的形式:
'2000-01-01'.replace(/(\d{4})-(\d{2})-(\d{2})/g, '$3-$2-$1');
//'01-01-2000'
反向引用
英文中不少单词都有重叠出现的字母,如 shoot
或 beep
。若想检查某个单词是否包含重叠出现的字母,则需要引入 反向引用(back-reference)
反向引用允许在正则表达式内部引用之前捕获分组匹配的文本,形式是 \num
,num
表示所引用分组的编号。
//重复字母
/([a-z])\1/
/([a-z])\1/.test('aa');
// true
/([a-z])\1/.test('ab');
// false
反向引用可以用于建立前后联系。HTML 标签的开始标签和结束标签是对应的。
// 开始标签
const startIndex = /<([^>]+)>/
// 标签内容
const content = /[\s\S]*?/
// 匹配成对的标签
const couple = /<([^>]+)>[\s\S]*?<\/\1>/
/<([^>]+)>[\s\S]*?<\/\1>/.test('<a>123</a>');
// true
/<([^>]+)>[\s\S]*?<\/\1>/.test('<a>123</b>');
// false
非捕获
除了捕获分组,正则表达式还提供了 非捕获分组(non-capturing group),以 (?:)
的形式表示,它只用于限定作用范围,而不捕获任何文本。
比如,要匹配 abcabc
这个字符,一般地,可以写为 (abc){2}
,但由于并不需要捕获文本,只是限定了量词的作用范围,所以应该写为 (?:abc){2}
。
/(abc){2}/.test('abcabc');
// true
/(?:abc){2}/.test('abcabc');
// true
由于非捕获分组不捕获文本,对应地,也就没有捕获组编号。
/(abc){2}/.test('abcabc');
// true
console.log(RegExp.$1);
// 'abc'
/(?:abc){2}/.test('abcabc');
// true
console.log(RegExp.$1);
// ''
非捕获分组也不可以使用反向引用。
/(?:123)\1/.test('123123');
// false
/(123)\1/.test('123123');
// true
捕获分组和非捕获分组可以在一个正则表达式中同时出现。
/(\d)(\d)(?:\d)(\d)(\d)/.exec('12345');
// ["12345", "1", "2", "4", "5"]
断言
在正则表达式中,有些结构并不真正匹配文本,而只负责判断在某个位置左/右侧是否符合要求,这种结构被称为 断言(assertion),也称为 锚点(anchor),常见的断言有 3 种:
- 单词边界
- 起始结束
- 环视
单词边界
在文本处理中可能会经常进行单词替换,比如把 row
替换成 line
。但是,如果直接替换,不仅所有单词 row
都被替换成 line
,单词内部的 row
也会被替换成 line
。要想解决这个问题,必须有办法确定单词 row
,而不是字符串 row
。
为了解决这类问题,正则表达式提供了专用的 单词边界(word boundary),记为 \b
,它匹配的是 单词边界 的位置,而不是字符。\b
匹配的是一边是单词字符 \w
,一边是非单词字符 \W
的位置
与 \b
对应的还有 \B
,表示非单词边界,但实际上 \B
很少使用
/\ban\b/.test('an apple');
// true
/\ban\b/.test('a an');
// true
/\ban\b/.test('an');
// true
/\ban\b/.test('and');
// false
/\ban\b/.test('ban');
// false
起始结束
常见的断言还有 ^
和 $
,它们分别匹配字符串的开始位置和结束位置,所以可以用来判断整个字符串能否由表达式匹配。
//匹配第一个单词
/^\w*/.exec('first word\nsecond word\nthird word');
// ['first']
//匹配最后一个单词
/\w*$/.exec('first word\nsecond word\nthird word');
// ['word']
/^a$/.test('a\n');
// false
/^a$/.test('a');
// true
^
和 $
的常用功能是删除字符串首尾多余的空白,类似于字符串 String
对象的 trim()
方法。
function fnTrim(str) {
str.replace(/^\s+|\s+$/, '');
}
console.log(fnTrim(' hello world '));
// 'hello world'
环视
环视(Look-around),可形象地解释为停在原地,四处张望。环视类似于单词边界,在它旁边的文本需要满足某种条件,而且本身不匹配任何字符。
环视分为 正序环视 和 逆序环视,而 JavaScript 只支持正序环视,相当于只支持向前看,不支持往回看。
而正序环视又分为 肯定正序环视 和 否定正序环视。
符号 | 描述 |
---|---|
x(?=y) |
肯定 正序环视,表示 x 后紧跟着 y 才匹配 |
x(?!y) |
否定 正序环视,表示 x 后不紧跟着 y 才匹配 |
<br />
/a(?=b)/.exec('abc');
// ['a']
/a(?=b)/.exec('ac');
// null
/a(?!b)/.exec('abc');
// null
/a(?!b)/.exec('ac');
// ['a']
/a(?=b)b/.exec('abc');
// ['ab']
环视虽然也用到括号,却与捕获型分组编号无关;但如果环视结构出现捕获型括号,则会影响分组。
/ab(?=cd)/.exec('abcd');
// ['ab']
/ab(?=(cd))/.exec('abcd');
// ['ab','cd']
匹配模式
匹配模式(Match Mode) 指匹配时使用的规则。设置特定的模式,可能会改变对正则表达式的识别。
不区分大小写模式
默认地,正则表达式是 区分大小写 的,通过设置标志 i
,可以 忽略大小写(ignore case)。
/ab/.test('aB');
// false
/ab/i.test('aB');
// true
多行模式
默认地,正则表达式中的 ^
和 $
匹配的是整个字符串的起始位置和结束位置,而通过设置标志 m
,开启多行模式,它们也能匹配字符串内部某一行文本的起始位置和结束位置。
// example 1
/world$/.test('hello world\n');
// false
/world$/m.test('hello world\n');
// true
// example 2
/^b/.test('a\nb');
// false
/^b/m.test('a\nb');
// true
全局模式
默认地,第一次匹配成功后,正则对象就停止向下匹配了。g
修饰符表示 全局匹配(global),设置 g
标志后,正则对象将匹配全部符合条件的结果,主要用于搜索和替换。
'1a,2a,3a'.replace(/a/, 'b');
// '1b,2a,3a'
'1a,2a,3a'.replace(/a/g, 'b');
// '1b,2b,3b'
优先级
下表为正则表达式符号优先级排序,从上到下,优先级逐渐降低(优先级数值越大,优先级越高)。
符号 | 符号名称 | 优先级 |
---|---|---|
\ |
转义符 | 5 |
() (?!) (?=) [] |
括号、字符集、环视 | 4 |
* + ? {n} {n,} {n,m} |
量词 | 3 |
^ $ |
起始结束位置 | 2 |
| |
选择 | 1 |
由于括号的用途之一就是为量词限定作用范围,所以优先级比量词高。
/ab{2}/.test('abab');
// false
/(ab){2}/.test('abab');
// true
选择符 |
的优先级最低,比起始和结束位置都要低。
/^ab|cd$/.test('abc');
// true
/^(ab|cd)$/.test('abc');
// false
局限性
尽管 JavaScript 中的正则表达式功能比较完备,但与其他语言相比,缺少某些特性
下面列出了 JavaScript 正则表达式不支持的特性
- POSIX 字符组(只支持普通字符组和排除型字符组)
- Unicode 支持(只支持单个 Unicode 字符)
- 匹配字符串开始和结尾的
\A
和\Z
锚(只支持^
和$
) - 逆序环视(只支持顺序环视)
- 命名分组(只支持 0-9 编号的捕获组)
- 单行模式和注释模式(只支持
m
、i
、g
) - 模式作用范围
- 纯文本模式