|
1 | 1 | # 前瞻断言与后瞻断言 |
2 | 2 |
|
3 | | -有时候我们需要匹配后面跟着特定模式的一段模式。比如,我们要从 `subject:1 turkey costs 30€` 这段字符中匹配价格数值。 |
| 3 | +有时我们只需要为一个模式找到那些在另一个模式之后或之前的匹配项。 |
4 | 4 |
|
5 | | -我们需要获取 `subject:€` 符号前面的数值(假设价格是整数)。 |
| 5 | +有一种特殊的语法,称为“前瞻断言(lookahead)”和“后瞻断言(lookbehind)”。 |
6 | 6 |
|
7 | | -那就是前瞻断言要做的事情。 |
| 7 | +首先,让我们从字符串中查找价格,例如 `subject:1 turkey costs 30€`。即:一个数字,后跟`subject:€`符号。 |
8 | 8 |
|
9 | 9 | ## 前瞻断言 |
10 | 10 |
|
11 | | -语法为:`pattern:x(?=y)`,它表示“仅在后面是 `pattern:y` 的情况匹配 `pattern:x`”。 |
| 11 | +语法为:`pattern:x(?=y)`,它表示“仅在后面是 `pattern:Y` 时匹配 `pattern:X`”。There may be any pattern instead of `pattern:X` and `pattern:Y`. |
12 | 12 |
|
13 | | -那么对于一个后面跟着 `€` 的整数金额,它的正则表达式应该为:`pattern:\d+(?=€)`。 |
| 13 | +那么对于一个后面跟着 `€` 的整数,正则表达式应该为:`pattern:\d+(?=€)`。 |
14 | 14 |
|
15 | 15 | ```js run |
16 | 16 | let str = "1 turkey costs 30€"; |
17 | 17 |
|
18 | | -alert( str.match(/\d+(?=€)/) ); // 30 (正确地跳过了单个的数字 1) |
| 18 | +alert( str.match(/\d+(?=€)/) ); // 30,数字 1 被忽略了,因为它后面没有 € |
19 | 19 | ``` |
20 | 20 |
|
21 | | -让我们来看另一种情况:这次我们想要一个数量,它是一个不被 `subject:€` 跟着的数值。 |
| 21 | +请注意:前瞻断言只是一个测试,括号 `pattern:(?=...)` 中的内容不包含在匹配结果 `match:30` 中。 |
22 | 22 |
|
23 | | -这里就要用到前瞻否定断言了。 |
| 23 | +当我们查找 `pattern:X(?=Y)` 时,正则表达式引擎会找到 `pattern:X`,然后检查其后是否有 `pattern:Y`。如果没有,则跳过潜在匹配,并继续搜索。 |
24 | 24 |
|
25 | | -语法为:`pattern:x(?!y)`,意思是 "查找 `pattern:x`, 但是仅在不被 `pattern:y` 跟随的情况下匹配成功"。 |
| 25 | +更复杂的测试也是可能的,例如 `pattern:X(?=Y)(?=Z)` 表示: |
| 26 | + |
| 27 | +1. 寻找 `pattern:X`。 |
| 28 | +2. 检查 `pattern:Y` 是否紧跟在 `pattern:X` 之后(如果不是则跳过)。 |
| 29 | +3. 检查 `pattern:Z` 是否也在 `pattern:X` 之后(如果不是则跳过)。 |
| 30 | +4. 如果两个测试都通过了,那么 `pattern:X` 是匹配的,否则继续搜索。 |
| 31 | + |
| 32 | +换句话说,这样的模式意味着我们同时在寻找 `pattern:X` 后跟 `pattern:Y` 和 `pattern:Z`。 |
| 33 | + |
| 34 | +这只有在模式 `pattern:Y` 和 `pattern:Z` 不是互斥的情况下才可行。 |
| 35 | + |
| 36 | +例如,`pattern:\d+(?=\s)(?=.*30)` 查找后跟着空格 `pattern:(?=\s)` 的 `pattern:\d+`,并且有 ` 30` 在它之后的某个地方 `pattern:(?=.*30)`: |
| 37 | + |
| 38 | +```js run |
| 39 | +let str = "1 turkey costs 30€"; |
| 40 | + |
| 41 | +alert( str.match(/\d+(?=\s)(?=.*30)/) ); // 1 |
| 42 | +``` |
| 43 | + |
| 44 | +在我们给出的字符串中,与数字 `1` 完全匹配。 |
| 45 | + |
| 46 | +## 否定的前瞻断言 |
| 47 | + |
| 48 | +假设我们想要一个数量,而不是来自同一字符串的价格。那是一个数字 `pattern:\d+`,后面不是 `subject:€`。 |
| 49 | + |
| 50 | +为此,我们可以使用否定的前瞻断言。 |
| 51 | + |
| 52 | +语法是:`pattern:X(?!Y)`,意思是“搜索 `pattern:X`,但前提是后面没有 `pattern:Y`”。 |
26 | 53 |
|
27 | 54 | ```js run |
28 | 55 | let str = "2 turkeys cost 60€"; |
29 | 56 |
|
30 | | -alert( str.match(/\d+(?!€)/) ); // 2(正确地跳过了价格) |
| 57 | +alert( str.match(/\d+\b(?!€)/g) ); // 2(价格不匹配) |
31 | 58 | ``` |
32 | 59 |
|
33 | 60 | ## 后瞻断言 |
34 | 61 |
|
| 62 | +```warn header="后瞻断言的浏览器兼容情况" |
| 63 | +请注意:非 V8 引擎的浏览器不支持后瞻断言,例如 Safari、Internet Explorer。 |
| 64 | +``` |
| 65 | + |
35 | 66 | 前瞻断言允许添加一个“后面要跟着什么”的条件判断。 |
36 | 67 |
|
37 | | -后瞻断言也是类似的,只不过它是在相反的方向上进行条件判断。也就是说,它只允许匹配前面有特定字符串的模式。 |
| 68 | +后瞻断言也类似,只不过它是在相反的方向上进行条件判断。也就是说,它只允许匹配前面有特定字符串的模式。 |
38 | 69 |
|
39 | | -语法为: |
40 | | -- 后瞻肯定断言:`pattern:(?<=y)x`, 匹配 `pattern:x`, 仅在前面是 `pattern:y` 的情况。 |
41 | | -- 后瞻否定断言:`pattern:(?<!y)x`, 匹配 `pattern:x`, 仅在前面不是 `pattern:y` 的情况。 |
| 70 | +语法为如下: |
| 71 | +- 肯定的后瞻断言:`pattern:(?<=Y)X`,匹配 `pattern:X`,仅在前面是 `pattern:Y` 的情况下。 |
| 72 | +- 否定的后瞻断言:`pattern:(?<!Y)X`,匹配 `pattern:X`,仅在前面不是 `pattern:Y` 的情况下。 |
42 | 73 |
|
43 | | -举个例子,让我们把价格换成美元。美元符号通常在数字之前,所以要查找 `$30` 我们将使用 `pattern:(?<=\$)\d+` —— 一个前面带 `subject:$` 的数值: |
| 74 | +例如,让我们把价格换成美元。美元符号通常在数字前面,所以要查找 `$30` 我们将使用 `pattern:(?<=\$)\d+` —— 一个前面带 `subject:$` 的数值: |
44 | 75 |
|
45 | 76 | ```js run |
46 | 77 | let str = "1 turkey costs $30"; |
47 | 78 |
|
48 | | -alert( str.match(/(?<=\$)\d+/) ); // 30 (跳过了单个的数字 1) |
| 79 | +// 美元符号被转义 \$ |
| 80 | +alert( str.match(/(?<=\$)\d+/) ); // 30(跳过了仅仅是数字的值) |
49 | 81 | ``` |
50 | 82 |
|
51 | | -另外,为了找到数量 —— 一个前面不带 `subject:$` 的数字,我们可以使用否定后瞻断言:`pattern:(?<!\$)\d+` |
| 83 | +如果我们需要找到量词 —— 一个前面不带 `subject:$` 的数字,我们可以使用否定的后瞻断言:`pattern:(?<!\$)\d+` |
52 | 84 |
|
53 | 85 | ```js run |
54 | 86 | let str = "2 turkeys cost $60"; |
55 | 87 |
|
56 | | -alert( str.match(/(?<!\$)\d+/) ); // 2 (跳过了价格) |
| 88 | +alert( str.match(/(?<!\$)\b\d+/g) ); // 2(价格不匹配) |
57 | 89 | ``` |
58 | 90 |
|
59 | 91 | ## 捕获组 |
60 | 92 |
|
61 | | -一般来说,环视断言括号中(前瞻和后瞻的通用名称)的内容不会成为匹配到的一部分结果。 |
| 93 | +一般来说,前瞻断言和后瞻断言括号中的内容不会成为结果的一部分。 |
62 | 94 |
|
63 | | -例如:在模式 `pattern:\d+(?!€)` 中,`pattern:€` 符号就不会出现在匹配结果中。 |
| 95 | +例如,在模式 `pattern:\d+(?!€)` 中,`pattern:€` 符号就不会出现在匹配结果中。这是很自然的事:我们寻找一个数字 `pattern:\d+`,而 `pattern:(?=€)` 只是一个测试,表示要匹配的数字后面应该紧跟着 `subject:€` 字符。 |
64 | 96 |
|
65 | | -但是如果我们想要捕捉整个环视表达式或其中的一部分,那也是有可能的。只需要将其包裹在另加的括号中。 |
| 97 | +但在某些情况下,我们可能还想捕获前瞻断言和后瞻断言所匹配的内容,或者部分内容。这也是可行的。只需要将该部分包装在额外的括号中。 |
66 | 98 |
|
67 | | -例如,这里货币符号 `pattern:(€|kr)` 和金额一起被捕获了: |
| 99 | +在下面的示例中,货币符号 `pattern:(€|kr)` 和金额一起被捕获了: |
68 | 100 |
|
69 | 101 | ```js run |
70 | 102 | let str = "1 turkey costs 30€"; |
71 | | -let reg = /\d+(?=(€|kr))/; // €|kr 两边有额外的括号 |
| 103 | +let regexp = /\d+(?=(€|kr))/; // €|kr 两侧有额外的括号 |
72 | 104 |
|
73 | | -alert( str.match(reg) ); // 30, € |
| 105 | +alert( str.match(regexp) ); // 30, € |
74 | 106 | ``` |
75 | 107 |
|
76 | 108 | 后瞻断言也一样: |
77 | 109 |
|
78 | 110 | ```js run |
79 | 111 | let str = "1 turkey costs $30"; |
80 | | -let reg = /(?<=(\$|£))\d+/; |
| 112 | +let regexp = /(?<=(\$|£))\d+/; |
81 | 113 |
|
82 | | -alert( str.match(reg) ); // 30, $ |
| 114 | +alert( str.match(regexp) ); // 30, $ |
83 | 115 | ``` |
84 | 116 |
|
85 | | - |
86 | 117 | ## 总结 |
87 | 118 |
|
88 | | -当我们想根据前面/后面的上下文筛选出一些东西的时候,前瞻断言和后瞻断言(通常被称为“环视断言”)对于简单的正则表达式就很有用。 |
| 119 | +当我们想根据前面/后面的上下文匹配某些内容的时候,前瞻断言和后瞻断言(通常被称为“环视断言”)很有用。 |
| 120 | + |
| 121 | +对于简单的正则表达式,我们可以手动执行类似的操作。即:不管上下文,匹配所有可匹配的内容,然后在循环中根据上下文进行过滤。 |
89 | 122 |
|
90 | | -有时我们可以手动处理来得到相同的结果,即:匹配所有,然后在循环中按上下文进行筛选。请记住,`str.matchAll` 和`reg.exec` 返回的匹配结果有 `.index` 属性,因此我们能知道它在文本中的确切位置。但通常正则表达式可以做得更好。 |
| 123 | +请记住,`str.match`(没有修饰符 `pattern:g`)和 `str.matchAll`(总是)将匹配项作为具有 `index` 属性的数组返回,因此我们知道它在文本中的确切位置,并且可以检查上下文。 |
91 | 124 |
|
92 | | -环视断言类型: |
| 125 | +但通常环视断言更方便。 |
| 126 | + |
| 127 | +环视断言类型: |
93 | 128 |
|
94 | 129 | | 模式 | 类型 | 匹配 | |
95 | 130 | |--------------------|------------------|---------| |
96 | | -| `pattern:x(?=y)` | 前瞻肯定断言 | `x` ,仅当后面跟着 `y` | |
97 | | -| `pattern:x(?!y)` | 前瞻否定断言 | `x` ,仅当后面不跟 `y` | |
98 | | -| `pattern:(?<=y)x` | 后瞻肯定断言 | `x` ,仅当跟在 `y` 后面 | |
99 | | -| `pattern:(?<!y)x` | 后瞻否定断言 | `x` ,仅当不跟在 `y` 后面 | |
100 | | - |
101 | | -前瞻断言也可用于禁用回溯。为什么它是需要的 - 请看下一章。 |
| 131 | +| `X(?=Y)` | 肯定的前瞻断言 | `pattern:X` 后紧跟着 `pattern:Y` | |
| 132 | +| `X(?!Y)` | 否定的前瞻断言 | `pattern:X` 后没紧跟着 `pattern:Y` | |
| 133 | +| `(?<=Y)X` | 肯定的后瞻断言 | `pattern:X` 紧跟在 `pattern:Y` 后面 | |
| 134 | +| `(?<!Y)X` | 否定的后瞻断言 | `pattern:X` 没紧跟在 `pattern:Y` 后面 | |
0 commit comments