7

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

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

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

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

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

4

5 回答 5

18

捕获组的两个用例

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

  1. 分组——如果你需要一个组被视为一个单一的实体,以便将一些东西应用于整个组。

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

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

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

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

    现在假设您要确保“地方”被称为“农场”“牧场”。如果不是这种情况,那么您不想提取兔子的数量(在正则表达式中 - 您不希望正则表达式匹配)。

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

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


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

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


关于关注点分离的几句话

抓包就是打破SoC原理的典型例子。这种语法结构有两个不同的目的。随着问题变得明显,引入了一个额外的构造( ?:) 来禁用这两个功能之一

这只是一个设计错误。也许是缺少“免费的特殊字符”起了作用……但这仍然是一个糟糕的设计。

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

于 2013-09-02T18:37:58.330 回答
11

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

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

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

input.match(/hello (?:world|there)/)
于 2013-09-02T18:09:46.933 回答
5

除了上面的答案,如果您正在使用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)/而交换/(?:\d)/结果:

["Hello ", " word. Sentence number ", "."]
于 2015-04-29T17:26:46.250 回答
3

当您想将修饰符应用于组时。

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

等等

于 2013-09-02T18:08:49.317 回答
3

当您需要条件并且不关心哪个选项导致匹配时使用它们。

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

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

于 2013-09-02T18:14:22.397 回答