为什么JavaScript正则表达式中的交替(管道)运算符(|)不能给我两个匹配项?

38

这是我的正则表达式:

"button:not([DISABLED])".match(/\([^()]+\)|[^()]+/g);

结果为:

["button:not", "([DISABLED])"]

这是正确的吗?我有点困惑。因为(管道)运算符|表示“或”,所以我认为正确的结果应该是:

["button:not", "[DISABLED]", "([DISABLED])"] 

因为这个:

["button:not", "[DISABLED]"]

是以下操作的结果:

"button:not([DISABLED])".match(/[^()]+/g);

而且这个:

["([DISABLED])"]

是以下操作的结果:

"button:not([DISABLED])".match(/\([^()]+\)/g);

但是控制台输出的结果告诉我结果是:

["button:not", "([DISABLED])"]

问题出在哪里?


@RobW 是的,因为这个问题根本不涉及JS。 - Tomalak
@RobW:这是括号内代码符号中的管道符号 :-/ - Felix Kling
请查看http://www.regular-expressions.info/alternation.html。 - Felix Kling
1
@Tomalak,我不知道RobW最初说了什么,但是所有正则表达式问题都应该提到它们适用的语言/环境/风格。即使一个单独的问题可能足够简单,适用于所有风格,我也不会阻止提及语言,因为这通常是必要的(而且OP如何知道其他风格是否做同样的事情)。 - Martin Ender
2
@user2155362 关于所有全局正则表达式应用程序,你需要知道的基本事情是:匹配永远不会重叠。就是这样。括号中的情况产生了你的第二个匹配,但你想要的第三个匹配与它重叠(实际上是它的子字符串),所以你不能用这种方式获得它。acdcjunior的答案提供了更多的推理。但要记住的是:匹配永远不会重叠 - Martin Ender
4个回答

62

正则表达式

/\([^()]+\)|[^()]+/g

基本上说:有两个选项,匹配(1)\([^()]+\) (2)[^()]+,无论您在哪里看到它们(/g)。

让我们迭代您的示例字符串,以便您了解所得结果背后的原因。

起始字符串:

button:not([DISABLED])

步骤:
  • 光标开始于字符 b (实际上它从字符串开头锚点 ^ 开始,但对于此示例无关紧要)
  • 在两个可用选项之间,b 只能匹配 (2),因为 (1) 需要一个起始的 (
    • 现在它已经开始匹配 (2),它将一直匹配到底,这意味着它将消耗所有不是 () 的东西。
    • 从上面的项目中,它消耗了一切直到(包括)t 字符(因为下一个字符是一个不匹配 [^()]+(,因此 留下 button:not 作为第一个匹配的字符串)。
  • (为提高清晰度预留空间)
  • 现在光标在 (。它是否开始匹配任何选项?是,第一个: \([^()]+\)
    • 同样,现在它已经开始匹配 (1),它将一直经过它,这意味着它将消耗所有不是 () 的东西,直到找到一个 ) (如果在消耗过程中发现了一个 () 之前,那么它将回溯,因为这意味着最终没有匹配 (1) 正则表达式)
    • 现在它继续消耗所有剩余的字符,直到找到 )留下 ([DISABLED]) 作为第二个匹配的字符串
  • (为提高清晰度预留空间)
  • 由于我们已经到达了最后一个字符,正则表达式处理结束。



编辑:有一个非常有用的在线工具,可以以图形形式查看正则表达式。也许它有助于理解正则表达式的工作原理:

Regular expression image

您可以逐步移动光标并查看我上面所解释的内容:实时链接
关于由|分隔的表达式优先级的注意事项:由于JavaScript正则表达式引擎处理字符串的方式,表达式出现的顺序很重要。它将按照给定的顺序评估每个替代方案。如果其中一个选项匹配到结尾,则不会尝试匹配任何其他选项,即使它可以。希望通过示例使其更清晰:
"aaa".match(/a|aa|aaa/g); // ==> ["a", "a", "a"]
"aaa".match(/aa|aaa|a/g); // ==> ["aa", "a"]
"aaa".match(/aaa|a|aa/g); // ==> ["aaa"]

哇,好清晰啊。谢谢你的回复,真的帮了我很多。谢谢! - user2155362

15

你对交替运算符的理解似乎有误。它并不会查找所有可能的匹配项,而只会查找从左到右的第一个匹配项。

考虑将(a | b)表示为“匹配要么a要么b

另请参阅:http://www.regular-expressions.info/alternation.html


但是对我来说,[^()]+|\([^()]+\)会给出相同的最长匹配结果([DISABLED]),而不是[DISABLED] - Muthu Ganapathy Nathan
1
这是因为字符串从左到右处理,\([^()]+\) 在字符串中较早的位置(在 ( 处)匹配并消耗了所有字符直到结尾。 - Felix Kling
简单的例子:"baa".match(/aa|baa/); 将匹配 baa,因为第一个选择 (aa) 不匹配第一个字符 (b),但第二个选择匹配。 - Felix Kling
感谢提供示例。所以,它是否类似于使用2个线程,在字符串中搜索其模式?而不是单个线程逐一搜索字符串中的2个模式? - Muthu Ganapathy Nathan
我不理解你的问题,也不知道你所说的“线程”是指什么。 - Felix Kling
显示剩余4条评论

0

我对正则表达式不是很擅长,但我认为它们的工作方式是给出与之匹配的一项内容,而不是所有可能与之匹配的内容。

因此,| 运算符表示:“给我一个与左侧正则表达式匹配的内容,或者一个与右侧正则表达式匹配的内容”。

由于你的字符串包含与左侧正则表达式匹配的内容,所以你只会得到那个内容。


-1

正则表达式找到的是最佳匹配,而不是所有可能的匹配。该正则表达式的最佳匹配是"([DISABLED])",而不是"[DISABLED]",后者是“更好”的匹配的子集。

考虑以下示例:

"123 456789".match( /[0-9]{4,6}/g )

你想找到一个介于4位和6位之间的数字。如果结果是所有与正则表达式匹配的可能数字,那么它就没有太多用处:

[ "4567", "5678", "6789", "45678", "56789", "456789" ]   // you don't want this

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接