你的方法有两个问题。
- 你的第一个前瞻应该是后顾。 当你写
(?!cat)
时,引擎会检查下一个三个字符是否为 cat
,然后重置到它开始的位置(这就是前瞻的原理),然后你再试图在同样的三个字符位置上匹配 dog
。因此,前瞻没有添加任何内容:如果你可以匹配到 dog
,那么显然你不能在同一位置匹配到 cat
。你想要的是后顾 (?<!cat)
,它检查前面的字符是否不是 cat
。不幸的是,JavaScript 不支持后顾。
- 你想要将两个前瞻逻辑“或”起来。 在你的情况下,如果任一前瞻失败,它都会使模式失败。因此,两个前瞻的要求(两端都不能有
cat
)必须同时满足。但实际上你想要将它们“或”起来。如果支持后顾,则会变成 (?<!cat)dog|dog(?!cat)
(请注意,交替分割整个模式)。但正如我所说,JavaScript 不支持后顾。你在第一个 catdogdog
的部分似乎将两个前瞻“或”起来的原因是因为前面的 cat
没有被检查(参见第1点)。
如何绕过后顾?Kolink 的答案建议使用 (?!cat)...dog
,它将前瞻放置在可能出现 cat
的位置,并使用前瞻。这有两个新问题:它无法匹配字符串开头的 dog
(因为需要前面的三个字符)。而且它不能匹配连续的两个 dog
,因为匹配不能重叠(在匹配第一个 dog
后,引擎需要三个新字符,即 ...
,这将在实际匹配到下一个 dog
之前匹配掉下一个 dog
)。
有时可以通过反转模式和字符串来绕过后顾,从而将后顾变成前瞻 - 但在你的情况下,这会将末尾的前瞻变成后顾。
仅使用正则表达式的解决方案
我们需要更聪明一些。由于匹配不能重叠,我们可以尝试显式地匹配
catdogcat
,而不是替换它(因此在目标字符串中跳过它们),然后只替换我们找到的所有
dog
。我们将这两种情况放在交替中,因此它们都会在字符串的每个位置上尝试(虽然在这里并不重要,但
catdogcat
选项具有优先权)。问题是如何获得条件替换字符串。但让我们先看看我们已经得到了什么:
text.replace(/(catdog)(?=cat)|dog/g, "$1[or 000 if $1 didn't match]")
所以在第一种替代方案中,我们匹配一个
catdog
并将其捕获到组
1
中,并检查是否有另一个
cat
跟随。在替换字符串中,我们只需写回
$1
即可。美妙的是,如果第二个替代匹配成功,则第一组将未被使用,因此在替换中将成为空字符串。之所以只匹配
catdog
并使用前瞻而不是立即匹配
catdogcat
,原因是重叠匹配。如果我们使用
catdogcat
,那么在输入
catdogcatdogcat
中,第一个匹配将消耗所有内容,直到包括第二个
cat
,因此第二个
dog
无法被第一种替代方案识别。
现在唯一的问题是,如果我们使用第二种替代方案,如何将
000
放入替换中。
不幸的是,我们无法创造出不属于输入字符串的条件替换。诀窍是在输入字符串的末尾添加
000
,如果我们找到
dog
,则在前瞻中捕获它,然后将其写回。
text.replace(/$/, "000")
.replace(/(catdog)(?=cat)|dog(?=.*(000))/g, "$1$2")
.replace(/000$/, "")
第一个替换将“000”添加到字符串末尾。
第二个替换匹配“catdog”(检查是否有另一个“cat”跟随)并将其捕获到组1中(将2留空),或匹配“dog”并将“000”捕获到组2中(将组1留空)。然后我们写回“$1$2”,它将是未装饰的“catdog”或“000”。
第三个替换消除了我们在字符串末尾的多余“000”。
回调解决方案
如果您不喜欢准备正则表达式和第二个选项中的前瞻,则可以使用稍微简单一些的正则表达式,并使用替换回调:
text.replace(/(catdog)(?=cat)|dog/g, function(match, firstGroup) {
return firstGroup ? firstGroup : "000"
})
使用
replace
的这个版本,每次匹配都会调用提供的函数,并将其返回值用作替换字符串。函数的第一个参数是整个匹配,第二个参数是第一个捕获组(如果该组未参与匹配,则其值将为
undefined
),以此类推...
因此,在替换回调中,如果
firstGroup
未定义(即匹配了
dog
选项),我们可以自由地产生
000
,或者如果存在
firstGroup
(即匹配了
catdogcat
选项),则只需返回
firstGroup
。这种方法更加简洁,可能更容易理解。但是,调用函数的开销使其速度
显著变慢(不过,这是否重要取决于您要多频繁地执行此操作)。选择你喜欢的方式吧!
cat
,而第二个(?!cat)
则将其排除。你可能需要解释一下你在尝试使用这两个先行断言实现什么功能,因为我怀疑第一个先行断言也无法达到你的预期效果。 - Martin Ender