前瞻断言:(?=...),(?!...)
**前瞻断言**“向前看”:它尝试使用给定模式匹配后续输入,但它不消耗任何输入——如果匹配成功,输入中的当前位置保持不变。
语法
(?=pattern)
(?!pattern)
参数
描述
正则表达式通常从左到右匹配。这就是为什么前瞻和后顾断言被称为这样的——前瞻断言右侧的内容,而后顾断言左侧的内容。
为了使 (?=pattern)
断言成功,pattern
必须匹配当前位置之后的文本,但当前位置不会改变。(?!pattern)
形式否定断言——如果 pattern
在当前位置不匹配,则它会成功。
pattern
可以包含捕获组。有关此情况下行为的更多信息,请参阅捕获组页面。
与其他正则表达式运算符不同,前瞻中没有回溯——此行为继承自 Perl。只有当 pattern
包含捕获组并且前瞻之后的模式包含对这些捕获的反向引用时,这才有意义。例如
/(?=(a+))a*b\1/.exec("baabac"); // ['aba', 'a']
// Not ['aaba', 'a']
上面模式的匹配如下
- 前瞻
(a+)
在"baabac"
中第一个"a"
之前成功,并且"aa"
被捕获,因为量词是贪婪的。 a*b
匹配"baabac"
中的"aab"
,因为前瞻不会消耗其匹配的字符串。\1
不匹配后续字符串,因为这需要 2 个"a"
,但只有一个可用。因此匹配器回溯,但它不会进入前瞻,因此捕获组不能减少到 1 个"a"
,并且整个匹配在此处失败。exec()
在下一个位置(第二个"a"
之前)重新尝试匹配。这次,前瞻匹配"a"
,并且a*b
匹配"ab"
。反向引用\1
匹配捕获的"a"
,并且匹配成功。
如果正则表达式能够回溯到前瞻并修改其中做出的选择,则匹配将在步骤 3 中通过 (a+)
匹配第一个 "a"
(而不是前两个 "a"
)和 a*b
匹配 "aab"
而成功,甚至不需要重新尝试下一个输入位置。
否定前瞻也可以包含捕获组,但反向引用仅在前瞻的 pattern
中才有意义,因为如果匹配继续,pattern
必然不匹配(否则断言失败)。这意味着在 pattern
之外,对否定前瞻中这些捕获组的反向引用始终成功。例如
/(.*?)a(?!(a+)b\2c)\2(.*)/.exec("baaabaac"); // ['baaabaac', 'ba', undefined, 'abaac']
上面模式的匹配如下
(.*?)
模式是非贪婪的,因此它从匹配空字符串开始。但是,下一个字符是a
,它无法匹配输入中的"b"
。(.*?)
模式匹配"b"
,以便模式中的a
匹配"baaabaac"
中的第一个"a"
。- 在此位置,前瞻成功匹配,因为如果
(a+)
匹配"aa"
,则(a+)b\2c
匹配"aabaac"
。这会导致断言失败,因此匹配器回溯。 (.*?)
模式匹配"ba"
,以便模式中的a
匹配"baaabaac"
中的第二个"a"
。- 在此位置,前瞻无法匹配,因为剩余的输入不遵循模式“任意数量的
"a"
、一个"b"
、相同数量的"a"
、一个c
”。这会导致断言成功。 - 但是,由于断言中没有匹配任何内容,因此
\2
反向引用没有值,因此它匹配空字符串。这导致剩余的输入被末尾的(.*)
消耗。
通常,断言不能量化。但是,在不区分 Unicode 的模式中,可以量化前瞻断言。这是一种用于 Web 兼容性的已弃用语法,您不应该依赖它。
/(?=a)?b/.test("b"); // true; the lookahead is matched 0 time
示例
匹配字符串而不消耗它们
有时,验证匹配的字符串后面是否跟着某些内容而不将其作为结果返回很有用。以下示例匹配后面跟着逗号/句点的字符串,但标点符号不包含在结果中
function getFirstSubsentence(str) {
return /^.*?(?=[,.])/.exec(str)?.[0];
}
getFirstSubsentence("Hello, world!"); // "Hello"
getFirstSubsentence("Thank you."); // "Thank you"
可以通过捕获您感兴趣的子匹配来实现类似的效果。
模式减法和交集
使用前瞻,您可以使用不同的模式多次匹配字符串,这使您可以表达复杂的关联,如减法(是 X 但不是 Y)和交集(既是 X 又是 Y)。
以下示例匹配任何标识符,但不是保留字(此处仅出于简洁起见显示了三个保留字;可以将更多保留字添加到此析取中)。[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*
语法准确地描述了语言规范中的标识符字符串集;您可以在词法语法和Unicode 字符类转义中阅读有关标识符和 \p
转义的更多信息。
function isValidIdentifierName(str) {
const re =
/^(?!(?:break|case|catch)$)[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/u;
return re.test(str);
}
isValidIdentifierName("break"); // false
isValidIdentifierName("foo"); // true
isValidIdentifierName("cases"); // true
以下示例匹配既是 ASCII 又可以用作标识符部分的字符串
function isASCIIIDPart(char) {
return /^(?=\p{ASCII}$)\p{ID_Start}$/u.test(char);
}
isASCIIIDPart("a"); // true
isASCIIIDPart("α"); // false
isASCIIIDPart(":"); // false
如果您正在对有限数量的字符进行交集和减法,则可能需要使用字符集交集语法,该语法由 v
标志启用。
规范
规范 |
---|
ECMAScript 语言规范 # prod-Assertion |
浏览器兼容性
BCD 表仅在启用 JavaScript 的浏览器中加载。