有时我们需要找到仅满足以下条件的模式匹配:该模式后面或前面跟着另一个模式。
有一种特殊的语法用于此,称为“前瞻”和“后顾”,统称为“环顾”。
首先,让我们从类似 1 turkey costs 30€
的字符串中找到价格。即:一个数字,后面跟着 €
符号。
前瞻
语法为:X(?=Y)
,表示“查找 X
,但仅在后面跟着 Y
时匹配”。X
和 Y
可以是任何模式。
对于一个整数后面跟着€
,正则表达式将是\d+(?=€)
let str = "1 turkey costs 30€";
alert( str.match(/\d+(?=€)/) ); // 30, the number 1 is ignored, as it's not followed by €
请注意:前瞻仅仅是一个测试,括号(?=...)
的内容不包含在结果30
中。
当我们查找X(?=Y)
时,正则表达式引擎会找到X
,然后检查它后面是否紧跟着Y
。如果不是,则跳过潜在的匹配,并继续搜索。
更复杂的测试是可能的,例如X(?=Y)(?=Z)
意味着
- 查找
X
。 - 检查
Y
是否紧跟在X
之后(如果不是,则跳过)。 - 检查
Z
是否也紧跟在X
之后(如果不是,则跳过)。 - 如果两个测试都通过了,那么
X
就是一个匹配,否则继续搜索。
换句话说,这种模式意味着我们正在寻找X
,后面紧跟着Y
和Z
。
只有当模式Y
和Z
不是相互排斥的时候才有可能。
例如,\d+(?=\s)(?=.*30)
查找\d+
,后面跟着一个空格(?=\s)
,并且在它之后某个地方有30
(?=.*30)
let str = "1 turkey costs 30€";
alert( str.match(/\d+(?=\s)(?=.*30)/) ); // 1
在我们的字符串中,它完全匹配数字1
。
负向先行断言
假设我们想要一个数量,而不是来自同一个字符串的价格。这是一个数字\d+
,后面不跟着€
。
为此,可以应用负向先行断言。
语法是:X(?!Y)
,它表示“搜索X
,但只有在后面不跟着Y
的情况下”。
let str = "2 turkeys cost 60€";
alert( str.match(/\d+\b(?!€)/g) ); // 2 (the price is not matched)
后行断言
请注意:后行断言在非 V8 浏览器(如 Safari、Internet Explorer)中不受支持。
先行断言允许为“后面是什么”添加条件。
后行断言类似,但它向后看。也就是说,它允许仅在它前面有某些内容的情况下匹配模式。
语法是
- 正向后查找:
(?<=Y)X
,匹配X
,但仅当它之前有Y
。 - 负向后查找:
(?<!Y)X
,匹配X
,但仅当它之前没有Y
。
例如,让我们将价格更改为美元。美元符号通常在数字之前,因此要查找$30
,我们将使用(?<=\$)\d+
- 一个以$
开头的金额。
let str = "1 turkey costs $30";
// the dollar sign is escaped \$
alert( str.match(/(?<=\$)\d+/) ); // 30 (skipped the sole number)
并且,如果我们需要数量 - 一个数字,而不是以$
开头,那么我们可以使用负向后查找(?<!\$)\d+
let str = "2 turkeys cost $60";
alert( str.match(/(?<!\$)\b\d+/g) ); // 2 (the price is not matched)
捕获组
通常,后视括号内的内容不会成为结果的一部分。
例如,在模式\d+(?=€)
中,€
符号不会作为匹配的一部分被捕获。这是自然的:我们寻找一个数字\d+
,而(?=€)
只是一个测试,它应该以€
结尾。
但在某些情况下,我们可能还想捕获后视表达式,或其一部分。这是可能的。只需将该部分包装在额外的括号中。
在下面的示例中,货币符号(€|kr)
与金额一起被捕获
let str = "1 turkey costs 30€";
let regexp = /\d+(?=(€|kr))/; // extra parentheses around €|kr
alert( str.match(regexp) ); // 30, €
以下是后视的相同情况
let str = "1 turkey costs $30";
let regexp = /(?<=(\$|£))\d+/;
alert( str.match(regexp) ); // 30, $
总结
前瞻和后视(通常称为“后视”)在我们希望根据它之前/之后的上下文匹配某些内容时很有用。
对于简单的正则表达式,我们可以手动执行类似的操作。也就是说:匹配任何上下文中的所有内容,然后在循环中按上下文过滤。
请记住,str.match
(没有标志g
)和str.matchAll
(始终)将匹配项作为带有index
属性的数组返回,因此我们知道它在文本中的确切位置,并且可以检查上下文。
但总的来说,后视更方便。
后视类型
模式 | 类型 | 匹配 |
---|---|---|
X(?=Y) |
正向前瞻 | X 如果后面跟着 Y |
X(?!Y) |
负向先行断言 | X 如果后面不跟着 Y |
(?<=Y)X |
正向后视 | X 如果在 Y 之后 |
(?<!Y)X |
负向后视 | X 如果不在 Y 之后 |
评论
<code>
标签,对于多行代码,请将其包装在<pre>
标签中,对于超过 10 行的代码,请使用沙箱 (plnkr,jsbin,codepen…)