一、背景
在一个夜深人静的晚上,公司前端扛把子卷不动了,打算下班!
项目经理眼疾手快立马拦住他说:大佬再帮我加个需求,明天就可以发布了!
前端扛把子:滚,我要回去睡觉了!
项目经理:要是帮我搞一下这个,周末给你介绍个女朋友!
前端扛把子虽然技术不错,奈何直到现在也没个女朋友,听项目经理这样说,两眼放光!说道:什么问题,今晚搞定。
项目经理心里暗暗说:你小子,果然好这口!转身把前端拉到自己工位上说到:我这里有一组数据,是个二维数组,但是里面有一些脏数据,我想把这些脏数据剔除掉,再发给后端就好了。所以要加一个数据清洗的功能!你看我给你画一下:
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版)》