Skip to content

正则引擎的匹配规则

正则引擎的匹配规则可以归纳为两点:

  1. 优先选择文本最左端的匹配结果;
  2. 标准量词优先匹配。

优先选择文本最左端的匹配结果

根据该规则,起始位置最靠左的匹配结果总是优先于其它可能的匹配结果,需要注意的是:该规则并未规定优先的匹配结果的长度,而只是规定,在所有的可能的匹配结果中,优先选择开始位置最左端的。

该规则的由来:匹配先从需要查找的字符串的起始位置尝试匹配。

此处“尝试匹配”的意思是

在当前位置测试整个正则表达式能匹配的每样文本。若在当前位置测试了所有可能之后不能找到匹配结果,就需要从字符串的第二个字符之前的位置开始重新尝试。在找到匹配结果以前必须在所有位置重复此过程,只有在尝试过所有的起始位置都不能找到匹配结果的情况下,才会宣告“匹配失败”。

现在我们可以回过头来看看之前字符组中怪异的例子了,先看这个最简单、最好理解的例子:

javascript
const regexp = /[Lo]/i; // 只要其中任意一个字符匹配成功,便终止匹配
const result = regexp.exec("AchooLuv");
console.log(result); // 匹配结果: ['o', index: 3]

按照表达式列出的字符顺序,为什么不是优先匹配字符L,而是匹配字符o,为了更好理解,现在我们把上例改一改:

javascript
const regexp = /L|o/i; // 不再使用字符组,改用多选分支
const result = regexp.exec("AchooLuv");
console.log(result); // 匹配结果依然是: ['o', index: 3]

用上面的匹配规则来解释这种现象就很好理解:为什么不是优先匹配字符L,而是匹配字符o了。

匹配规则与流程:

  1. 在匹配时,基于表达式的传统型NFA引擎,首先在字符串AchooLuv的第一个字符A处,测试表达式中的Lo是否能够匹配;
  2. 显然不能匹配,则在下一位置,字符c处再次测试;
  3. 依然不能满足匹配,继续向后移动;
  4. 直到在字符串中第一个字符o处,测试表达式中的Lo时,此时字符o满足匹配,结束整个匹配。

再看其中的后一个例子,也是同样的道理:

javascript
const regexp = /[Lo]{2}/i; // 或者写成 /(?:L|o){2}/
const result = regexp.exec("AchooLuv");
console.log(result); // 匹配结果: ['oo', index: 3]

标准量词优先匹配

标准匹配量词都是“匹配优先”的,被这些量词修饰的表达式,在匹配成功之前,进行尝试的次数存在上限和下限,而该规则规定,这些尝试总是希望获得最长的匹配(即贪婪模式)。

匹配优先量词之所以得名,是因为它们总是匹配多于匹配成功下限的字符!匹配优先量词虽然首先会尽可能匹配多的字符,但是为了整个表达式能够匹配成功,通常它们会不情愿的交还一些已匹配的字符。当然交还绝不能破坏匹配成立必须的条件,比如不允许零匹配的+。来看个简单的例子:

javascript
const regexp = /^.*(\d{2})/; // 至少捕获两个数字
const result = regexp.exec("Copyright 2017");
console.log(RegExp.$1); // 匹配结果为: 17

上例中,.*匹配整个字符串Copyright 2017,但是\d{2}必须匹配,此时.*开始交还字符7\d{2}中第一个\d满足匹配,但是第二个\d不能满足匹配,.*继续交还字符1,此时\d{2}满足匹配,并被捕获,匹配结束。