Saber 酱的抱枕

Fly me to the moon

05/11
2020
学习

JS 中 exec 和 match 进行正则表达式匹配的区别

设想一下简单的例子:有字符串 abcd,我们使用正则表达式 /\w/g 全局匹配它,应该获取到 a b c d 四个字符串。

正则表达式的 exec 方法和字符串的 match 都可以达到这个目的,但使用方式上有差别。

下面的内容只探讨全局匹配模式的情况(也就是正则表达式后面加了 g 标识)。非全局匹配模式,可以基本认为它们没有差别。


1. 返回值的格式不同

match 方法可以一次获取所有结果:

JS 中使用 exec 和 match 进行正则表达式匹配

match 的返回值是 RegExpMatchArray | null,在全局匹配时就是数组。

exec 的返回值则不同:

JS 中使用 exec 和 match 进行正则表达式匹配

exec 的返回值有一些额外的属性,可以在需要的时候使用 exec

(在非全局匹配时,matchexec 的结果一致。)


2. 一次执行,获取的结果数量不同

当我使用 exec 的时候,发现怎么只返回了第一个匹配结果?

exec 的返回值和 match 类似,有结果就是数组,没有结果就是 null。但是 exec 单次匹配只返回一个结果,而非我之前想的,和 match 一样返回所有结果。


3. 运行原理不同(lastIndex)

如果我们要用 exec 获取所有结果,需要把它执行多次;并且需要把正则表达式保存到变量里。像下面这样:

JS 中使用 exec 和 match 进行正则表达式匹配

可以看到,exec 每次只返回一个结果。一开始它会从字符串开始的地方进行匹配,之后每次执行都从上次匹配结束的位置继续往下匹配。当这个字符串全部匹配过一次之后,再执行返回的是 null,此时位置会复位到 0,匹配结束。

匹配结束之后再执行的话,又将从头开始匹配,重复这个过程。

上面提到了“位置”,这个位置是保存在正则表达式对象的 lastIndex 属性里的。

JS 中使用 exec 和 match 进行正则表达式匹配

你也可以手动修改这个属性,然后让正则表达式从你指定的位置开始匹配。

上面的例子中需要把正则表达式保存到变量里,这样才能储存每次的 lastIndex。如果不保存到变量,直接执行 /\w/g.exec('abcd'),每次执行都相当于初始化了表达式 /\w/g,初始的 lastIndex 为 0,所以这样匹配永远都只能匹配到第一个结果。


上面说了 exec 匹配多个结果需要多次执行,我们可以使用循环来优化这个操作。

JS 中使用 exec 和 match 进行正则表达式匹配

这里用了一个变量 result 保存每次执行的结果,以便我们使用。当所有匹配项都查找完毕,最后 exec 会返回一次 null,循环就结束了。


4. 正则表达式有分组时,结果不同

JS 中使用 exec 和 match 进行正则表达式匹配

当正则表达式有分组时,match 会返回整个表达式的匹配,忽略分组。这不就抓瞎了吗?

exec 不仅包含整个表达式的匹配,还包括分组结果。

可以看到 exec 里是有两个结果的,这才是我们想要的结果。

而且我本来就是打算将结果 0 替换成 结果 1,也就是 string.replace('[chapter:标题111]', '标题111'),显然这时候 exec 就很方便了,而 match 无法直接这样替换。


5. exec 要留意字符串的动态变化

exec 执行是一步步执行的,而不是像 match 那样一次执行完毕,所以 exec 需要留意字符串的动态变化。

还是上面的例子:

JS 中使用 exec 和 match 进行正则表达式匹配

这个表达式应该能匹配两次,每次都把 chapter 标签换成里面的标题文字。预期结果是 "标题111+标题222"

实际上可以看到只替换了第一次,这是为什么呢?

因为代码在执行中动态改变了 str,导致第二次匹配不到了,第二次结果就是 null

还记得 exec 一步步执行靠的是 lastIndex 吗?第一次执行完, lastIndex 是 15,之后 str 变了,前面变短了,现在的 15 已经是 “标题222” 的开始处,导致第二次匹配不到。

解决办法就是把 lastIndex 复位,也就是那一行注释,取消掉注释,每次执行完复位 lastIndex,这样可以正常匹配到第二处,问题解决了。

这么想的话,其实 exec 里动态修改字符串参数的话,一定要复位 lastIndex,这样才是最保险的。


6. 使用场景不同

注意只讨论全局匹配的场景。非全局匹配时,它俩似乎没啥区别。

如果觉得选择困难的话,全都用 exec。因为 exec 显得更全能一些。

详细一点分析的话:

逻辑很简单的,用 match
逻辑稍复杂的,用 exec

想要一步就得出结果数组的,用 match
不需要保存结果数组,直接循环每一个匹配结果,用 exec

正则表达式有分组,必须用 exec


表面上看起来 match 是最简单的,毕竟一次匹配到了所有的结果:

JS 中使用 exec 和 match 进行正则表达式匹配

但实际使用中,获取结果只是第一步,往往还要进行两步操作:

  1. 判断结果(结果不是 null,才能继续操作)
  2. 对结果进行循环

这样下来 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 了。

JS 中 exec 和 match 进行正则表达式匹配的区别