繁体   English   中英

Javascript 正则表达式中被动(非捕获)组的目的是什么?

[英]What is the purpose of the passive (non-capturing) group in a Javascript regex?

Javascript 正则表达式中被动组的用途是什么?

被动组以问号冒号开头:(?: (?:group)

换句话说,这两件事看起来是一样的:

"hello world".match(/hello (?:world)/)
"hello world".match(/hello world/)

在什么情况下你需要非捕获组,为什么?

捕获组的两个用例

正则表达式中的捕获组实际上有两个不同的目标(正如名称“捕获组”本身所暗示的那样):

  1. 分组 - 如果您需要将一个组视为单个实体 ,以便将一些内容应用于整个组。

    可能最简单的例子是包括一个可选的字符序列,例如“foo”可选地后跟“bar” ,正则表达式: /foo(bar)?/ (捕获组)或/foo(?:bar)?/ (非捕获组)。 请注意尾随? 应用于整个组(bar) (在这种情况下由一个简单的字符序列bar组成)。 如果您只是想检查输入是否与正则表达式匹配,那么无论您使用捕获组还是非捕获组都无关紧要 - 它们的行为相同(除非非捕获组略快 )。

  2. 捕获 - 如果您需要提取输入的一部分

    例如,你想从一个输入中获得一些兔子 ,例如“农场包含8头奶牛和89头兔子” (我不知道英语不是很好)。 正则表达式可能是/(\\d+)\\s*rabbits\\b/ 成功匹配后,您可以从JavaScript代码 (或任何其他编程语言) 获取捕获组匹配的值

    在此示例中,您有一个捕获组,因此您可以通过索引0访问它(有关详细信息,请参阅此答案 )。

    现在想象你想确保“地方”被称为“农场”“牧场” 如果不是这样,那么你不想提取兔子的数量(用正则表达式 - 你不希望正则表达式匹配)。

    所以你重写你的正则表达式如下:/( /(farm|ranch).*\\b(\\d+)\\s*rabbits\\b/ .* /(farm|ranch).*\\b(\\d+)\\s*rabbits\\b/ 正则表达式本身就可以工作,但是你的JavaScript被破坏了 - 现在有两个捕获组 ,你必须改变你的代码来获得第二个捕获组的内容,以获得兔子的数量(即将索引从0改为1)。 第一组现在包含字符串“farm”或“ranch”,您不打算提取它。

    一个非捕获组来救援:/( /(?:farm|ranch).*\\b(\\d+)\\s*rabbits\\b//(?:farm|ranch).*\\b(\\d+)\\s*rabbits\\b/ 。*( /(?:farm|ranch).*\\b(\\d+)\\s*rabbits\\b/ 它仍然匹配“农场”或“牧场”,但不捕获它 ,因此不会移动后续捕获组的索引 并且您的JavaScript代码可以正常工作而无需更改


该示例可能过于简单,但考虑到您拥有一个包含许多组的非常复杂的正则表达式,并且您希望仅捕获其中的少数几个。 非捕获组真的很有帮助 - 您不必计算所有组(仅捕获组)。

此外,非捕获组用于文档目的 :对于读取代码的人,非捕获组表示您对提取内容不感兴趣,您只是想确保它匹配。


关于分离关注点的几句话

捕获组打破SoC原则的典型示例。 此语法构造有两个不同的用途 随着问题变得明显,引入了另外的构造?: :)以禁用两个特征中的一个

这只是一个设计错误 也许缺乏“免费特殊角色”扮演了它的角色......但它仍然是一个糟糕的设计。

正则表达式是一个非常古老,强大且广泛使用的概念。 出于向后兼容性的原因,现在不太可能修复这个漏洞。 这只是关注点分离重要性的一个教训。

非捕获与“正常”(捕获)组只有一个区别:它们不需要正则表达式引擎记住它们匹配的内容。

用例是有时你必须(或应该)使用一个组,不是因为你对它所捕获的内容感兴趣而是出于语法原因。 在这些情况下,使用非捕获组而不是“标准”捕获组是有意义的,因为它不太占用资源 - 但如果您不关心它,捕获组将以完全相同的方式运行。

您的具体示例并不能很好地使用非捕获组,因为这两个表达式完全相同。 一个更好的例子可能是:

input.match(/hello (?:world|there)/)

除了上面的答案,如果您使用String.prototype.split()并使用捕获组,则输出数组包含捕获的结果( 请参阅MDN )。 如果您使用不会发生的非捕获组。

var myString = 'Hello 1 word. Sentence number 2.';
var splits = myString.split(/(\d)/);

console.log(splits);

输出:

["Hello ", "1", " word. Sentence number ", "2", "."]

而交换/(\\d)/ for /(?:\\d)/导致:

["Hello ", " word. Sentence number ", "."]

如果要将修改器应用于组。

/hello (?:world)?/
/hello (?:world)*/
/hello (?:world)+/
/hello (?:world){3,6}/

等等

在需要条件时使用它们,而不关心哪个选项导致匹配。

非捕获组可以简化匹配复杂表达式的结果。 这里,组1始终是名称发言者。 如果没有非捕获组,说话者的姓名可能会在第1组或第2组中结束。

/hello (?:world|foobar )?said (.+)/

我刚刚发现它的不同用途。 我试图捕获一个嵌套组,但希望将重复组的整个集合作为一个元素:

所以对于AbbbbC

(A)((?:b)*)(C)

给出三组A bbbb C

对于AC也给出三组A null C

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM