慎用正则表达式test!!

lxf2023-04-20 11:48:01

一、背景

在一个夜深人静的晚上,公司前端扛把子卷不动了,打算下班!

项目经理眼疾手快立马拦住他说:大佬再帮我加个需求,明天就可以发布了!

前端扛把子:滚,我要回去睡觉了!

项目经理:要是帮我搞一下这个,周末给你介绍个女朋友!

前端扛把子虽然技术不错,奈何直到现在也没个女朋友,听项目经理这样说,两眼放光!说道:什么问题,今晚搞定。

项目经理心里暗暗说:你小子,果然好这口!转身把前端拉到自己工位上说到:我这里有一组数据,是个二维数组,但是里面有一些脏数据,我想把这些脏数据剔除掉,再发给后端就好了。所以要加一个数据清洗的功能!你看我给你画一下:


const arr = [
  ['123','456','789'],
  ['234','-','567'],
  ['890','*','?'],
]

在发给后端的时候要把这些非数字的过滤掉,这个比较急,所以今晚您老帮忙加急弄一下呗。

前端扛把子心想,这么简单的功能,那不得分分钟就搞定么!女朋友这不马上就到手了么,哈哈!接着他洋洋洒洒,一顿操作猛如虎,写下了以下代码


const arr = [
  ['123','456','789'],
  ['234','-','567'],
  ['890','*','?'],
]

const reg = /[^0-9]/g

const clearData = (arr: string[][])=>{
  const rowLen = arr.length;
  for(let i = 0 ; i< rowLen ; i++){
    const columnLen = arr[i].length;
    for(let j = 0;j< columnLen;j++){
      if(reg.test(arr[i][j])){
        arr[i][j] = ""
      }
    }
  }
  return arr;
}

前端扛把子再一次看了看自己写的代码,自顾自的说道:相当优雅,特别考虑到每次循环时对于.length的访问不能太频繁,将其提升到外层,性能也是不错的。转身叫道:我写好了,那个什么,小姐姐微信给我推一下呗!

项目经理正在打王者荣耀,不快的说道:来了,这么快就好啦,不愧是前端扛把子呀,不过你先别猴急,我验证了再说呀!

前端扛把子内心想说道这代码还能出错,就你事多!然后出于礼貌的说道:行吧行吧!你试试吧。

项目经理点击清洗之后,特别不忿的说到道:为啥每次都要清洗多次才能全部清洗完呀!

前端扛把子说:怎么可能,我看看

// 运行返回结果

[
  ['123','456','789'],
  ['234','','567'],
  ['890','','?'], // 怎么还剩一个漏网之鱼没有被搞掉,他妹的
]

前端扛把子再次检查了一下自己的代码;

const arr = [
  ['123','456','789'],
  ['234','-','567'],
  ['890','*','?'],
]

const reg = /[^0-9]/g

const clearData = (arr: string[][])=>{
  const rowLen = arr.length;
  for(let i = 0 ; i< rowLen ; i++){
    const columnLen = arr[i].length;
    for(let j = 0;j< columnLen;j++){
      if(reg.test(arr[i][j])){
        arr[i][j] = ""
      }
    }
  }
  return arr;
}

这代码看破天也没什么问题呀!遍历每一个元素,如果符合正则就进行替换,这么简单的逻辑还能出错!看着项目经理鄙视的眼神,前端扛把子瞬间脸红了,这已经不是介绍女朋友的事儿了,说什么今天也要证明一下自己前端扛把子的地位。

他立马说道:项目经理你先回,女朋友我无所谓了,明天早上你会看到完整且没有问题的功能!说罢便自顾自的查起了文档

过了几个钟头,前端扛把子研读了几乎正则的所有文档,大彻大悟。

二、思考

以下是前端扛把子的内心过程:

原来是因为这个lastIndex的问题,从何说起呢?

当我们在使用字面量创建了一个正则表达式的时候,相当于初始化了一个对象,这个对象上有一个叫做lastIndex的属性,来看一下它的定义。

  • 只有正则表达式使用了表示全局检索的 "g" 或者粘性检索的 "y" 标志时,该属性才会起作用;
  • 如果 regexp.test 和 regexp.exec 匹配成功,lastIndex 会被设置为紧随最近一次成功匹配的下一个位置。
  • 如果 regexp.test 和 regexp.exec 匹配失败,lastIndex 会被设置为 0;

我们来看一个例子

const reg = /[^0-9]/g;

reg.test('a')  // true

reg.lastIndex // 1

reg.test('a') // false

reg.lastIndex // 0

可以发现对于同一个正则表达式,匹配同一个字符串时也会有不同的结果,其核心原因是因为该正则表达式的lastIndex是在不断变化的,而下一次匹配是从lastIndex的位置开始匹配的,因此会出现不同的结果。

同样会有这个特性的还有exec方法

const reg = /[^0-9]/g;

reg.exec('a')  // ['a', index: 0, input: 'a', groups: undefined]

reg.lastIndex // 1

reg.exec('a') // null

reg.lastIndex // 0

所以今后再使用以上方法的时候一定要小心。那项目经理的问题如何解决呢?

三、解决方法

方法一:每次使用最新的正则表达式

const arr = [
  ['123','456','789'],
  ['234','-','567'],
  ['890','*','?'],
]

const clearData = (arr: string[][])=>{
  const rowLen = arr.length;
  for(let i = 0 ; i< rowLen ; i++){
    const columnLen = arr[i].length;
    for(let j = 0;j< columnLen;j++){
      if(/[^0-9]/g.test(arr[i][j])){
        arr[i][j] = ""
      }
    }
  }
  return arr;
}

这种方法可以倒是可以,但是每次循环都创建一个正则表达式,性能并不会很好!

方法二:去掉修饰符g

const arr = [
  ['123','456','789'],
  ['234','-','567'],
  ['890','*','?'],
]

const reg = /[^0-9]/

const clearData = (arr: string[][])=>{
  const rowLen = arr.length;
  for(let i = 0 ; i< rowLen ; i++){
    const columnLen = arr[i].length;
    for(let j = 0;j< columnLen;j++){
      if(reg.test(arr[i][j])){
        arr[i][j] = ""
      }
    }
  }
  return arr;
}

针对这个业务场景倒是没什么问题,不过需求如果换成对于每个字符串元素,都要将错误字符替换成某个特定字符,就不可以了,这个时候必须使用修饰符进行字符串的整体匹配,所以这种方式也是有局限性的。

方法三:每次强制从开始位置进行匹配

const arr = [
  ['123','456','789'],
  ['234','-','567'],
  ['890','*','?'],
]

const reg = /[^0-9]/g

const clearData = (arr: string[][])=>{
  const rowLen = arr.length;
  for(let i = 0 ; i< rowLen ; i++){
    const columnLen = arr[i].length;
    for(let j = 0;j< columnLen;j++){
      reg.lastIndex = 0;
      if(reg.test(arr[i][j])){
        arr[i][j] = ""
      }
    }
  }
  return arr;
}

这种方式倒是不错的,即不用每次都创建新的正则表达式,而且对于每一个单元字符串也可以进行全局匹配,应该是最好的方案了吧!

四、尾声

前端扛把子最终把代码整理了一下,测试了功能没有问题了,然后默默的收拾了书包发现已经凌晨2点了!

他再一次捍卫了自己前端扛把子死磕的精神,但是他知道他哪是什么前端扛把子呀!一个简单的正则他工作这么久了,今天也才把这个lastIndex搞明白,所以没什么扛把子的程序员,无非是不断学习,保持一颗时刻学习的心才是最重要的!

走在一个人的大街上,他感到很冷,突然手机铃声一响!他看到项目经理给他推了个微信,并发了一段消息:

微信推给你啦,这个是我表妹,看你小子工作很认真,对技术也有死磕精神,好好把握机会,哥只能帮你到这里啦!

是的,这一次,前端扛把子必须把握好机会,加油!

五、作者近期文章

《用零碎时间个人建站》 《从0到1开发一个浏览器插件》 《Chrome插件的通信(V3版)》