正则引擎的匹配规则
正则引擎的匹配规则可以归纳为两点:
- 优先选择文本最左端的匹配结果;
- 标准量词优先匹配。
优先选择文本最左端的匹配结果
根据该规则,起始位置最靠左的匹配结果总是优先于其它可能的匹配结果,需要注意的是:该规则并未规定优先的匹配结果的长度,而只是规定,在所有的可能的匹配结果中,优先选择开始位置最左端的。
该规则的由来:匹配先从需要查找的字符串的起始位置尝试匹配。
此处“尝试匹配”的意思是
在当前位置测试整个正则表达式能匹配的每样文本。若在当前位置测试了所有可能之后不能找到匹配结果,就需要从字符串的第二个字符之前的位置开始重新尝试。在找到匹配结果以前必须在所有位置重复此过程,只有在尝试过所有的起始位置都不能找到匹配结果的情况下,才会宣告“匹配失败”。
现在我们可以回过头来看看之前字符组中怪异的例子了,先看这个最简单、最好理解的例子:
const regexp = /[Lo]/i; // 只要其中任意一个字符匹配成功,便终止匹配
const result = regexp.exec("AchooLuv");
console.log(result); // 匹配结果: ['o', index: 3]
按照表达式列出的字符顺序,为什么不是优先匹配字符L
,而是匹配字符o
,为了更好理解,现在我们把上例改一改:
const regexp = /L|o/i; // 不再使用字符组,改用多选分支
const result = regexp.exec("AchooLuv");
console.log(result); // 匹配结果依然是: ['o', index: 3]
用上面的匹配规则来解释这种现象就很好理解:为什么不是优先匹配字符L
,而是匹配字符o
了。
匹配规则与流程:
- 在匹配时,基于表达式的
传统型NFA
引擎,首先在字符串AchooLuv
的第一个字符A
处,测试表达式中的L
和o
是否能够匹配; - 显然不能匹配,则在下一位置,字符
c
处再次测试; - 依然不能满足匹配,继续向后移动;
- 直到在字符串中第一个字符
o
处,测试表达式中的L
和o
时,此时字符o
满足匹配,结束整个匹配。
再看其中的后一个例子,也是同样的道理:
const regexp = /[Lo]{2}/i; // 或者写成 /(?:L|o){2}/
const result = regexp.exec("AchooLuv");
console.log(result); // 匹配结果: ['oo', index: 3]
标准量词优先匹配
标准匹配量词都是“匹配优先”的,被这些量词修饰的表达式,在匹配成功之前,进行尝试的次数存在上限和下限,而该规则规定,这些尝试总是希望获得最长的匹配(即贪婪模式)。
匹配优先量词之所以得名,是因为它们总是匹配多于匹配成功下限的字符!匹配优先量词虽然首先会尽可能匹配多的字符,但是为了整个表达式能够匹配成功,通常它们会不情愿
的交还一些已匹配的字符。当然交还
绝不能破坏匹配成立必须的条件,比如不允许零匹配的+
。来看个简单的例子:
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}
满足匹配,并被捕获,匹配结束。