原文链接:http://javascript.info/regexp-groups,translate with ❤️ by zhangbao.
模式的一部分可以用圆括号 (...)
包围,这称为”捕获分组”。
会有两个结果:
当使用
String#match
或者RegExp#exec
方法的时候,它允许将完整匹配里的分组捕获匹配项,放入最终返回的匹配数组里。如果我们在圆括号后面使用了量词,圆括号里的内容就会被看成一个整体,而不是最后一个字符了。
例子
下面例子模式里的 (go)+
是查找一个或多一个的 ‘go
‘:
alert( 'Gogogo now!'.match(/(go)+/i) ); // Gogogo,go
如果没有圆括号的话,模式 /go+/
表示的是 g
后面跟一个或者重复多个的字母 o
。例如,goooo
或者 gooooooooo
。
圆括号里将两个字母 (go)
组合在一起了。
让我们再举一个更复杂点的例子——匹配 Email 的正则表达式。
Email 的例子:
my@mail.com
john.smith@site.com.uk
模式如:[-.\w]+@([\w-]+\.)+[\w-]{2,20}
。
第一部分是
@
符号前面的部分:单词、点和连字符,即[-.\w]
,像John.smith
。然后是
@
。然后是域名。可能是一个二级域名
site.com
或者带子域名的host.site.com.uk
。我们可以用”一个单词后面跟一个点”重复匹配若干次来表示,用于满足存在子域名的情况:mail.
或者site.com.
,最后一部分就是”单词”.com
或者.uk
这样的单词了。
单词后面跟一个点用 (\w+\.)+
(重复)。最后一个单词不包含点,所以只用了 \w{2,20}
,量词 {2,20}
限制 的是长度,因为域名块像 .uk
、.com
或者 .meseum
这些单词的长度是不能超过 20 个字符的。
所以匹配就变成了 (\w+\.)+\w{2,20}
,现在将 \w
替换成 [\w-]
,因为连字符在域名里也是被允许的,所以就得到了最终看到的结果。
其实这个正则表达式并不完美,但用是可以用的。足够好来修正错误或偶尔的失误。
例如,下面就是用了这个正则表达式查找字符串里的所有 Email 地址:
let reg = /[-.\w]+@([\w-]+\.)+[\w-]{2,20}/g;
alert("my@mail.com @ his@site.com.uk".match(reg)); // my@mail.com,his@site.com.uk
圆括号里的内容
圆括号是从左到右编号的,搜索引擎记忆住每个圆括号里的匹配项内容,用于在模式或者替换字符串里引用。
例如,我们可以使用模式 <.+?>
模式匹配 HTML 标签,通常我们会想要在结果之后做一些事情。
如果我们将 <...>
里的内容用圆括号括起来,我们可以得到这样的结果·:
let str = '<h1>你好,世界!</h1>'
let reg = /<(.+?)>/;
alert( str.match(reg) ); // <h1>,h1
String#macth
方法只有在正则表达式没有 g
标记的情况下,才会返回分组匹配信息。
如果我们需要知道所有的分组匹配信息的话,就要用到 正则和字符串方法 一章里介绍的 RegExp.exec 方法:
let str = '<h1>Hello, world!</h1>';
// 匹配两种情况: 开始标签 <h1> 和关闭标签 </h1>。
let reg = /<(.+?)>/g;
let match;
while (match = reg.exec(str)) {
// 第一个匹配项:<h1>,h1
// 第二个匹配项:</h1>,/h1
alert(match);
}
现在我们使用 <(.+?)>
找到了两个人匹配项,每个分组匹配结果都与完整匹配结果放在一个数组里了。
内嵌分组
圆括号可以内嵌,这种情况下编号还是从左到右的。
例如,我们搜索标签 <span class="my">
,需要得到的结果里包含:
标签里的整个内容:
span class="my"
。标签名:
span
。标签特性:
class="my"
。
下面我们来写表达式:
let str = 'span class="my"';
let reg = /<(([a-z]+)\s*([^>]*)>/;
let result = str.match(reg);
alert( result ); // <span class="my">, span class="my", span, class="my"
分组情况是这样的:
结果 result
里的第一个元素总是完整匹配的那个项目。
分组,是从做到右编号的,第一个分组对应 result[1]
,包含的是整个标签内容。
result[2]
对应第二个分组结果,也就是第二个开启 ( 到最近关闭 ) 的位置,包含的是标签名。我们不需要捕获空格,所以标签特性内容分在了 result[3]
里。
如果一个分组是可选的,并且不存在匹配内容,那么对应 result
的索引仍然存在(值为 undefined
)。
例如,考虑正则 a(z)?(c)?
,它查找 a
后面可选的 z
和 c
。
如果我们用 a
去 match 这个正则的话,会得到这样的结果:
let match = 'a'.match(/a(z)?(c)?/);
alert( match.length ); // 3
alert( match[0] ); // a (整个匹配项)
alert( match[1] ); // undefined
alert( match[2] ); // undefined
结果数组的长度为 3,但是所有的分组匹配内容都是没有的。
这里还有一个针对 ack
这个字符串的更加复杂的匹配:
let match = 'ack'.match(/a(z)?(c)?/)
alert( match.length ); // 3
alert( match[0] ); // ac (整个匹配项)
alert( match[1] ); // undefined, 因为 (z)? 没有匹配的内容
alert( match[2] ); // c
数组的长度还是 3,但是由于 (z)?
没有任何匹配内容,所有最终的结果是 ["ac", undefined, "c"]
。
不要捕获分组用 ?:
有时我们需要括号来正确地应用量词,但是我们不希望它们的内容出现在结果数组中。
可以在每个分组的开头添加 ?:
表示在结果数组中排除此分组统计。
例如,我们要查找 (go)+
,但又不希望将内容 (go)
记住在结果里,就可以写成 (?:go)+
。
在下面的例子中,我们只获得”John
“这个名称作为第二个分组在结果数组的一个成员:
let str = 'Gogo John';
// 将 Gogo 排除在分组之外
let reg = /(?:go)+ (\w+)/i;
let result = str.match(reg);
alert( result.length );
alert( result[1] ); // John
(完)