Skip to content

前瞻断言与后瞻断言

前言

有时候我们需要匹配后面跟着特定模式的一段模式。比如,我们要从 1 turkey costs 30€ 这段字符中匹配价格数值。

我们需要获取 符号前面的数值(假设价格是整数)。

那就是前瞻断言要做的事情。

前瞻断言

语法为:x(?=y),它表示 “匹配 x, 仅在后面是 y 的情况"”

那么对于一个后面跟着 的整数金额,它的正则表达式应该为:\d+(?=€)

js
let str = "1 turkey costs 30€";

// 前瞻断言
console.log(str.match(/\d+(?=€)/)); // 30 (正确地跳过了单个的数字 1)
// [ '30', index: 15, input: '1 turkey costs 30€', groups: undefined ]

让我们来看另一种情况:这次我们想要一个数量,它是一个不被 跟着的数值。

这里就要用到前瞻否定断言了。

语法为:x(?!y),意思是 “查找 x, 但是仅在不被 y 跟随的情况下匹配成功”。

js
let str = "2 turkeys cost 60€";

// 前瞻否定断言
console.log(str.match(/\d+(?!€)/)); // 2(正确地跳过了价格)
// [ '2', index: 0, input: '2 turkeys cost 60€', groups: undefined ]

后瞻断言

前瞻断言允许添加一个“后面要跟着什么”的条件判断。

后瞻断言也是类似的,只不过它是在相反的方向上进行条件判断。也就是说,它只允许匹配前面有特定字符串的模式。

语法为:

  • 后瞻肯定断言:(?<=y)x, 匹配 x, 仅在前面是 y 的情况。
  • 后瞻否定断言:(?<!y)x, 匹配 x, 仅在前面不是 y 的情况。

举个例子,让我们把价格换成美元。美元符号通常在数字之前,所以要查找 $30 我们将使用 (?<=\$)\d+ —— 一个前面带 $ 的数值:

js
let str = "1 turkey costs $30";

// 后瞻断言
console.log(str.match(/(?<=\$)\d+/)); // 30 (跳过了单个的数字 1)
// [ '30', index: 16, input: '1 turkey costs $30', groups: undefined ]

另外,为了找到数量 —— 一个前面不带 $ 的数字,我们可以使用否定后瞻断言:(?<!\$)\d+

js
let str = "2 turkeys cost $60";

// 后瞻否定断言
console.log(str.match(/(?<!\$)\d+/)); // 2 (跳过了价格)
// [ '2', index: 0, input: '2 turkeys cost $60', groups: undefined ]

捕获组

一般来说,环视断言括号中(前瞻和后瞻的通用名称)的内容不会成为匹配到的一部分结果。

例如:在模式 \d+(?!€) 中, 符号就不会出现在匹配结果中。

但是如果我们想要捕捉整个环视表达式或其中的一部分,那也是有可能的。只需要将其包裹在另加的括号中。

例如,这里货币符号 (€|kr) 和金额一起被捕获了:

js
let str = "1 turkey costs 30€";
let reg = /\d+(?=(€|kr))/; // €|kr 两边有额外的括号

console.log(str.match(reg)); // 30, €
/*
[
  '30',
  '€',
  index: 15,
  input: '1 turkey costs 30€',
  groups: undefined
]
*/

后瞻断言也一样:

js
let str = "1 turkey costs $30";
let reg = /(?<=(\$|£))\d+/;

console.log(str.match(reg)); // 30, $
/*
[
  '30',
  '$',
  index: 16,
  input: '1 turkey costs $30',
  groups: undefined
]
*/

请注意,对于后瞻断言,顺序保持不变,尽管前瞻括号在主模式之前。

通常括号是从左到右编号,但是后瞻断言是一个例外,它总是在主模式之后被捕获。所以 \d+ 的匹配会首先进入结果数组,然后是 (\$|£)

总结

当我们想排除掉左边,然后再排除右边,取中间的一部分字符串时,使用前瞻断言或者后瞻断言(通常被称为“环视断言”)就很方便了。比如:

js
let str = `CREATE TABLE \`t_role\`  (`
/*
有一个建表的sql语句,想匹配它的表名
前面是固定的 CREATE TABLE `
后面也是固定的 `  (
我们只想取中间的表名
就可以使用前瞻和后瞻断言
*/

let reg = /(?<=CREATE TABLE `)\w+(?=`  \()/
console.log(str.match(reg)); // t_role
/*
[
  't_role',
  index: 14,
  input: 'CREATE TABLE `t_role`  (',
  groups: undefined
]
*/
  1. 前瞻肯定断言x(?=y):表示我只想得到前边的x,且x的后面需要紧跟着y
  2. 前瞻否定断言x(?!y):表示我只想得到前边的x,且后面不能是y
  3. 后瞻肯定断言(?<=y)x:表示我只想得到后边的x,且x的前面必须是y
  4. 后瞻否定断言(?<!y)x:表示我只想得到后边的x,且x的前面不能是y

虽然前瞻和后瞻断言也是用括号包裹的,但是他们不会记作为捕获组,如果想获取到它们匹配到的值,可以使用括号把需要匹配的结果再包裹一下,比如:

  1. x(?=(y|z))
  2. x(?!(y|z))
  3. (?<=(y|z))x
  4. (?!=(y|z))x

参考

https://zh.javascript.info/regexp-lookahead-lookbehind