设想一下简单的例子:有字符串 abcd,我们使用正则表达式 /\w/g 全局匹配它,应该获取到 a b c d 四个字符串。
正则表达式的 exec 方法和字符串的 match 都可以达到这个目的,但使用方式上有差别。
下面的内容只探讨全局匹配模式的情况(也就是正则表达式后面加了 g 标识)。非全局匹配模式,可以基本认为它们没有差别。
match 方法可以一次获取所有结果:
match 的返回值是 RegExpMatchArray | null,在全局匹配时就是数组。
exec 的返回值则不同:
exec 的返回值有一些额外的属性,可以在需要的时候使用 exec 。
(在非全局匹配时,match 和 exec 的结果一致。)
当我使用 exec 的时候,发现怎么只返回了第一个匹配结果?
exec 的返回值和 match 类似,有结果就是数组,没有结果就是 null。但是 exec 单次匹配只返回一个结果,而非我之前想的,和 match 一样返回所有结果。
如果我们要用 exec 获取所有结果,需要把它执行多次;并且需要把正则表达式保存到变量里。像下面这样:
可以看到,exec 每次只返回一个结果。一开始它会从字符串开始的地方进行匹配,之后每次执行都从上次匹配结束的位置继续往下匹配。当这个字符串全部匹配过一次之后,再执行返回的是 null,此时位置会复位到 0,匹配结束。
匹配结束之后再执行的话,又将从头开始匹配,重复这个过程。
上面提到了“位置”,这个位置是保存在正则表达式对象的 lastIndex 属性里的。
你也可以手动修改这个属性,然后让正则表达式从你指定的位置开始匹配。
上面的例子中需要把正则表达式保存到变量里,这样才能储存每次的 lastIndex。如果不保存到变量,直接执行 /\w/g.exec('abcd'),每次执行都相当于初始化了表达式 /\w/g,初始的 lastIndex 为 0,所以这样匹配永远都只能匹配到第一个结果。
上面说了 exec 匹配多个结果需要多次执行,我们可以使用循环来优化这个操作。
这里用了一个变量 result 保存每次执行的结果,以便我们使用。当所有匹配项都查找完毕,最后 exec 会返回一次 null,循环就结束了。
当正则表达式有分组时,match 会返回整个表达式的匹配,忽略分组。这不就抓瞎了吗?
而 exec 不仅包含整个表达式的匹配,还包括分组结果。
可以看到 exec 里是有两个结果的,这才是我们想要的结果。
而且我本来就是打算将结果 0 替换成 结果 1,也就是 string.replace('[chapter:标题111]', '标题111'),显然这时候 exec 就很方便了,而 match 无法直接这样替换。
exec 执行是一步步执行的,而不是像 match 那样一次执行完毕,所以 exec 需要留意字符串的动态变化。
还是上面的例子:
这个表达式应该能匹配两次,每次都把 chapter 标签换成里面的标题文字。预期结果是 "标题111+标题222"。
实际上可以看到只替换了第一次,这是为什么呢?
因为代码在执行中动态改变了 str,导致第二次匹配不到了,第二次结果就是 null。
还记得 exec 一步步执行靠的是 lastIndex 吗?第一次执行完, lastIndex 是 15,之后 str 变了,前面变短了,现在的 15 已经是 “标题222” 的开始处,导致第二次匹配不到。
解决办法就是把 lastIndex 复位,也就是那一行注释,取消掉注释,每次执行完复位 lastIndex,这样可以正常匹配到第二处,问题解决了。
这么想的话,其实 exec 里动态修改字符串参数的话,一定要复位 lastIndex,这样才是最保险的。
注意只讨论全局匹配的场景。非全局匹配时,它俩似乎没啥区别。
如果觉得选择困难的话,全都用 exec。因为 exec 显得更全能一些。
详细一点分析的话:
逻辑很简单的,用 match。
逻辑稍复杂的,用 exec。
想要一步就得出结果数组的,用 match。
不需要保存结果数组,直接循环每一个匹配结果,用 exec。
正则表达式有分组,必须用 exec。
表面上看起来 match 是最简单的,毕竟一次匹配到了所有的结果:
但实际使用中,获取结果只是第一步,往往还要进行两步操作:
这样下来 match 未必占优。如下:
// match
const result = "abcd".match(/\w/g)
if (result) {
for (const str of result) {
console.log(str)
}
}
// exec
const reg = /\w/g
let result
while ((result = reg.exec("abcd"))) {
console.log(result[0])
}
因为 while 兼具了判断和循环两个步骤,如果需要循环的话,exec 更方便一些。
如果要使用分组的话,就更需要 exec 了。