非捕获组:(?:...)
非捕获组将子模式分组,允许你对整个组应用量词,或在其中使用或运算符。它的作用类似于 JavaScript 表达式中的分组运算符,与捕获组不同,它不记住匹配的文本,从而提供更好的性能,并避免当模式中也包含有用的捕获组时造成的混淆。
语法
正则表达式
(?:pattern)
参数
示例
将子模式分组并应用量词
在以下示例中,我们测试文件路径是否以 styles.css 或 styles.[a hex hash].css 结尾。由于整个 \.[\da-f]+ 部分是可选的,为了对其应用 ? 量词,我们需要将其分组到一个新的原子中。使用非捕获组通过不创建我们不需要的额外匹配信息来提高性能。
js
function isStylesheet(path) {
return /styles(?:\.[\da-f]+)?\.css$/.test(path);
}
isStylesheet("styles.css"); // true
isStylesheet("styles.1234.css"); // true
isStylesheet("styles.cafe.css"); // true
isStylesheet("styles.1234.min.css"); // false
分组或运算符
或运算符在正则表达式中具有最低的优先级。如果你想将或运算符作为更大模式的一部分使用,你必须将其分组。除非你依赖于或运算符匹配的文本,否则建议使用非捕获组。以下示例使用与输入边界断言文章相同的代码来匹配文件扩展名
js
function isImage(filename) {
return /\.(?:png|jpe?g|webp|avif|gif)$/i.test(filename);
}
isImage("image.png"); // true
isImage("image.jpg"); // true
isImage("image.pdf"); // false
避免重构风险
捕获组通过它们在模式中的位置访问。如果你添加或删除捕获组,则必须更新其他捕获组的位置,如果你通过匹配结果或反向引用访问它们。这可能是错误的来源,特别是如果大多数组纯粹用于语法目的(应用量词或分组或运算符)。使用非捕获组可以避免此问题,并允许轻松跟踪实际捕获组的索引。
例如,假设我们有一个函数,用于匹配字符串中的 title='xxx' 模式(示例取自捕获组)。为了确保引号匹配,我们使用反向引用来引用第一个引号。
js
function parseTitle(metastring) {
return metastring.match(/title=(["'])(.*?)\1/)[2];
}
parseTitle('title="foo"'); // 'foo'
如果我们后来决定添加 name='xxx' 作为 title= 的别名,我们将需要将或运算符分组到另一个组中
js
function parseTitle(metastring) {
// Oops — the backreference and index access are now off by one!
return metastring.match(/(title|name)=(["'])(.*?)\1/)[2];
}
parseTitle('name="foo"'); // Cannot read properties of null (reading '2')
// Because \1 now refers to the "name" string, which isn't found at the end.
与其定位所有引用捕获组索引的地方并逐一更新它们,不如避免使用捕获组
js
function parseTitle(metastring) {
// Do not capture the title|name disjunction
// because we don't use its value
return metastring.match(/(?:title|name)=(["'])(.*?)\1/)[2];
}
parseTitle('name="foo"'); // 'foo'
命名捕获组是避免重构风险的另一种方式。它允许通过自定义名称访问捕获组,当其他捕获组添加或删除时,该名称不受影响。
规范
| 规范 |
|---|
| ECMAScript® 2026 语言规范 # prod-Atom |
浏览器兼容性
加载中…