这篇文章上次修改于 228 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

不是所有的正则都需要在末尾加上 /g 的,因为这会导致出现一些你难以想象的问题...

事由

我做了一个上传组件,并在今天将其从 React 版本迁移成原生 JS 版。在测试的时候输入了很多 console.log 用于 Debug,结果发现了奇怪的一幕,注释掉 console.log 前后的代码执行结果竟然是不一致的!见了鬼了!

// props.allowed 的值是 /jpeg|png|gif|pdf/g
// 正则匹配
if(props.allowed && props.allowed instanceof RegExp){
  console.log("reg", props.allowed.test(files[0].type));

  if(props.allowed.test(files[0].type)){
    console.log("success");
    failed("filetype1", files[0].type);
    return;
  }
  else{
    console.log("wrong")
  }
}

具体表现就是,上传的文件类型(files[0].type)是 png,然而却没有输出「success」,而是「wrong」。但如果把 console.log("reg", props.allowed.test(files[0].type)); 这行注释掉,就会恢复正常。

分析

难不成 .test 是异步的?上下文执行返回的结果不一样?在控制台运行,test 的执行是「几乎」同步的,不然肯定返回 Promise 了...

为什么说是「几乎」呢?因为在某些特殊情况(DOM 操作)下,执行是存在较大延迟的。例如你前面刚刚创建一个元素,就打算去尝试删除它...

我把这个奇怪的问题告诉了 @Innei,他说这个之前也踩过坑,其实是 /g 的情况下,返回的是一个迭代器,每次运行的结果是不一样的。

例如下面的代码,第一次执行是 true,第二次返回的就是 false 了。用 execmatch 就看的更明显。

字符串有一个符合条件的结果,是这样的:

let reg = /beauty/g;

reg.test("Dear my beauty")
// true

reg.test("Dear my beauty")
// false

reg.test("Dear my beauty")
// true

reg.test("Dear my beauty")
// false

字符串有多个符合条件的结果,是这样的;

let reg = /beauty/g;

reg.test("Dear my beauty, and beauty")
// true

reg.test("Dear my beauty, and beauty")
// true

reg.test("Dear my beauty, and beauty")
// false

可以看出来,这里确实是迭代器,每次执行会按照匹配结果的顺序来,直到匹配结束返回 false

解决方案

去掉正则里面的 /g 就可以了。只需要检测「是否包含符合条件的值」的情况下(匹配一个结果),则无需使用 /g

那么

什么时候才适合使用 /g 呢?需要内容进行批量替换的时候。

"beauty & beauty".replace(/beauty/g, "cute")
// "cute & cute"

"beauty & beauty".replaceAll(/beauty/g, "cute")
// "cute & cute"

"beauty & beauty".replace(/beauty/, "cute")
// "cute & beauty"

"beauty & beauty".replaceAll(/beauty/, "cute")
// 报错 non-global RegExp argument