在方括号 […]
中的几个字符或者字符类表示“搜索给定字符中的任意一个”。
集合
例如,[eao]
表示以下 3 个字符中的任何一个:'a'
、'e'
或 'o'
。
这就是所谓的 集合。在正则表达式中,可以将集合和常规字符一起使用。
// 查找 [t 或 m],然后匹配 "op"
alert( "Mop top".match(/[tm]op/gi) ); // "Mop", "top"
请注意,虽然集合中有多个字符,但它们在匹配中只会对应其中的一个。
所以在下面的示例中并没有匹配项:
// 查找 "V",然后匹配 [o 或 i],之后匹配 "la"
alert( "Voila".match(/V[oi]la/) ); // null,无匹配项
这个模式会搜索:
V
,- 然后匹配其中的 一个字符
[oi]
, - 然后匹配
la
。
所以可以匹配上 Vola
或者 Vila
。
范围
方括号也可以包含 字符范围。
例如,[a-z]
表示从 a
到 z
范围内的字符,[0-5]
表示从 0
到 5
的数字。
在下面的示例中,我们将搜索首先是 "x"
,然后有两位数或两个在 A
到 F
范围内的字符紧随其后的字符串。
alert( "Exception 0xAF".match(/x[0-9A-F][0-9A-F]/g) ); // xAF
[0-9A-F]
中有两个范围:它搜索一个字符,该字符要么是在 0
到 9
范围内的数字,要么是从 A
到 F
的字母。
如果我们还想查找小写字母,则可以添加范围 a-f
:[0-9A-Fa-f]
。或添加标志 i
。
我们也可以在 […]
中使用字符类。
例如,如果我们想查找单词字符 \w
或连字符 -
,则集合可以写为 [\w-]
。
也可以组合多个类,例如 [\s\d]
表示“空格字符或数字”。
例如:
- \d —— 和
[0-9]
相同, - \w —— 和
[a-zA-Z0-9_]
相同, - \s —— 和
[\t\n\v\f\r ]
外加少量罕见的 Unicode 空格字符相同。
示例:多语言 \w
由于字符类 \w
是简写的 [a-zA-Z0-9_]
,因此无法找到中文象形文字,西里尔字母等。
我们可以编写一个更通用的模式,该模式可以查找任何语言中的单词字符。借助 Unicode 属性很容易实现:[\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}]
。
让我们理解一下。类似于 \w
,我们正在制作一组属于我们自己的包含具有以下 Unicode 属性的字符:
Alphabetic
(Alpha
) —— 字母,Mark
(M
) —— 音调,Decimal_Number
(Nd
) —— 数字,Connector_Punctuation
(Pc
) —— 下划线'_'
和类似的字符,Join_Control
(Join_C
) —— 两个特殊代码200c
和200d
,用于连字,例如阿拉伯语。
使用示例:
let regexp = /[\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}]/gu;
let str = `Hi 你好 12`;
// 找出所有字母和数字:
alert( str.match(regexp) ); // H,i,你,好,1,2
当然,我们可以编辑这个模式:添加 Unicode 属性或删除它们。Unicode:修饰符 "u" 和类 \p{...} 一文更详细地介绍了 Unicode 属性。
IE 浏览器未实现 Unicode 属性 p{...}
。如果我们真的需要它们,可以使用库 XRegExp。
或者只是使用我们感兴趣的语言中的字符范围,例如西里尔字母范围 [а-я]
。
排除范围
除了普通的范围匹配,还有像这样 [^…]
的“排除”范围匹配。
通过在开头添加插入符号 ^
来表示匹配所有 除了给定的字符 之外的任意字符。
例如:
[^aeyo]
—— 匹配除了'a'
、'e'
、'y'
或'o'
之外的任何字符。[^0-9]
—— 匹配除了数字之外的任何字符,与\D
作用相同。[^\s]
—— 匹配任何非空格字符,与\S
作用相同。
下面的示例搜索除了字母、数字和空格之外的任何字符:
alert( "alice15@gmail.com".match(/[^\d\sA-Z]/gi) ); // @ and .
[…] 中的转义
通常当我们想要准确地找到一个特殊字符时,我们需要像 \.
这样对其进行转义。如果我们需要反斜杠,那么我们需要使用 \\
,等等。
在方括号,我们可以使用绝大多数特殊字符而无需转义:
- 符号
. + ( )
无需转义。 - 在开头或结尾(未定义范围)的连字符
-
不会被转义。 - 插入符号
^
仅在开头会被转义(表示排除)。 - 右方括号
]
总是会被转义(如果我们需要寻找那个符号)。
换句话说,除了在方括号中有特殊含义的字符外,其它所有特殊字符都是允许不转义的。
方括号中的点 .
表示的就是一个点。模式 [.,]
将会搜索字符之一:点或逗号。
在下面的示例中,正则表达式 [-().^+]
查找 -().^+
中的任何字符:
// 不需要转义
let reg = /[-().^+]/g;
alert( "1 + 2 - 3".match(reg) ); // 匹配 +,-
……但是如果你为了“以防万一”转义了它们,这也不会有任何问题:
// 转义其中的所有字符
let reg = /[\-\(\)\.\^\+]/g;
alert( "1 + 2 - 3".match(reg) ); // 仍能正常工作:+,-
范围和修饰符 “u”
如果集合中有代理对(surrogate pairs),则需要标志 u
才能使它们正常工作。
例如,让我们在字符串 𝒳
中查找 [𝒳𝒴]
:
alert( '𝒳'.match(/[𝒳𝒴]/) ); // 显示了一个奇怪的字符,像 [?]
//(搜索执行不正确,返回了半个字符)
结果不正确,因为默认情况下正则表达式“不知道”代理对。
正则表达式引擎认为 [𝒳𝒴]
—— 不是两个字符,而是四个字符:
𝒳
的左半部分(1)
,𝒳
的右半部分(2)
,𝒴
的左半部分(3)
,𝒴
的右半部分(4)
。
我们可以看到它们的编码,如下所示:
for(let i=0; i<'𝒳𝒴'.length; i++) {
alert('𝒳𝒴'.charCodeAt(i)); // 55349, 56499, 55349, 56500
};
因此,上面的示例查找并显示了 𝒳
的左半部分。
如果我们添加了修饰符 u
,那么行为就正常了:
alert( '𝒳'.match(/[𝒳𝒴]/u) ); // 𝒳
当我们查找范围时也会出现类似的情况,就像 [𝒳-𝒴]
。
如果我们忘记添加修饰符 u
,则会出现错误:
'𝒳'.match(/[𝒳-𝒴]/); // Error: Invalid regular expression
因为,没有修饰符 u
时,代理对将被视为两个字符,所以 [𝒳-𝒴]
被理解为 [<55349><56499>-<55349><56500>]
(每个代理对都替换为其代码)。现在很容易看出范围 56499-55349
是无效的:其起始代码 56499
大于终止代码 55349
。这就是错误的原因。
带有修饰符 u
时,该模式就可以正常匹配了:
// 查找从 𝒳 到 𝒵 的字符
alert( '𝒴'.match(/[𝒳-𝒵]/u) ); // 𝒴