同事遇到了这样一个场景,用正则进行循环判断,简化的代码大概是下面这样:
var reg = /ab/g
console.log(reg.test('ab')) // true
console.log(reg.test('ab')) // false
这里是不是有人会觉得奇怪,为什么第二个是 false 呢。
test()
其实如果我们仔细看过MDN上关于test的描述,应该就知道了:

关于这个lastIndex,很多小伙伴可能没有听过也没有用过,让我们看看下面的代码:
var reg = /ab/g
console.log(Object.getOwnPropertyDescriptors(reg))
//{lastIndex: {value: 0, writable: true, enumerable: false, configurable: false}}
可以看到,reg 含有一个不可枚举的 lastIndex 属性。当然,如果你直接用 Object.keys(reg) 是看不到的:
var reg = /ab/g
console.log(Object.keys(reg)) // []
(知识点:getOwnPropertyDescriptors() 和 keys() 的区别是什么呢。keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组。)
这个就是造成上面第二个是false的原因。因为每执行一次,其实我们的lastIndex都会变化:
var reg = /[ab]/g
console.log('lastIndex', reg.lastIndex) // lastIndex 0
reg.test('ab')
console.log('lastIndex', reg.lastIndex) // lastIndex 1
reg.test('ab')
console.log('lastIndex', reg.lastIndex) // lastIndex 2
reg.test('ab')
console.log('lastIndex', reg.lastIndex) // lastIndex 0
根据上面的代码可以看到,这个lastIndex是会循环的。
所以第二次就出了问题。如果我们想让一开始的代码正常工作,该怎么办呢?
var reg = /ab/g
console.log(reg.test('ab')) // true
reg.lastIndex = 0
console.log(reg.test('ab')) // true
我们需要在每次执行后手动设置lastIndex。
test() 应用场景
我们举一个应用场景:后端传回来了一个图片 url 的数组,有 jpg、png 各种格式的,我们需要拿到以 jpg 结尾的图片:
首先,我们看看下面的代码:
const reg = /\.jpg/g;
const arr = [
'test0.png',
'test1.jpg',
'test2.jpg',
'test3.jpg',
'test4.jpg',
'test5.jpg',
];
const res = arr.filter(item => reg.test(item));
(关于判断图片格式的这个正则大家不要纠结,其实是不完善的,比如url的名字是 ajpg.jpg 这种也可以匹配到,这个不重要,不是本文重点)
乍一看,觉得没毛病。我们看看结果:

WTF???
你肯定反应过来了,因为lastIndex变了,所以和预期不一致了,我们输出一下看看:

可以看到,true和false是依次输出的,所以我们没有拿到全部的jpg格式的url。
还有,就是如果当前匹配失败了,lastIndex也会重置为0,这一点也很重要,不然后面的就匹配不到了。
所以解决方法之一就是手动重置lastIndex:

其实,最好的方法就是直接去掉g,因为这种场景其实是不需要加上g的。这样lastIndex就一直是0,不会变了。结果也是符合预期的:

exec
此外,lastIndex 还和另一个正则方法 exec() 也是好兄弟,关系很近。用法和test()原理一样。
很多人分不清 exec 和 match 的关系,或者可能没有用过 exec。
match 和 exec的关系:
首先,两个方法的来源是不一样的:
String.prototype.match()
RegExp.prototype.exec()
然后,不加g的时候是一样的:

然后,加了g的话:
match就没有捕获组了:

这个时候我们就需要用exec()方法了。
捕获组
关于捕获组,这里简单介绍一下。
捕获组分为四类:
捕获 ()
不捕获 (?:)
正向捕获 (?=)
反向捕获 (?!)
捕获组的功能很强大,这里就举两个例子:
例1:对日期进行任意格式的转换
var reg = /(\d{2})\/(\d{2})\/(\d{4})/
var data = '10/24/2017'
data = data.replace(reg, '$3/$1.$2')
console.log(data) //2017/10.24
例2: 千分位数字
'12345678'.replace(/(?=(\d{3})+$)/g, ',') // 12,345,678
因此,exec结合捕获组就可以实现很多复杂的逻辑。
exec() 应用场景
举个例子,我们要把一个字符串 a1b2c3d4e5里面的偶数的数字变成0。(方法肯定很多,这里只考虑用正则的方式)
肯定有同学说直接用 replace 就行了:
'a1b2c3d4e5'.replace(/[2468]/g,0) // "a1b0c3d0e5"
(因为这里的转换条件比较简单,如果是很复杂的转换,比如我要把偶数的数字先转成ASCII码,然后反转呢?主要就是意思一下哈。)

我个人觉得exec比match强大的地方在于它可以深入到内部,暂停执行,类似 generate 函数,而不像match一样是个黑盒。
exec() 和 lastIndex
关于这个,其实业务中感觉用的不多,就举个例子吧:
var regex = /[a-z]_[a-z]/ig;
var str = 'a_b_c';
var match;
while ((match = regex.exec(str)) !== null) {
console.log('match', match)
regex.lastIndex--
}
// a_b
// b_c
代码大家肯定看得懂,我就不说了。match 是无法实现这样的功能的,因为匹配完就丢弃了。
使用exec() 要注意的点就是不要写出死循环(毕竟有while)
同事遇到了这样一个场景,用正则进行循环判断,简化的代码大概是下面这样:
这里是不是有人会觉得奇怪,为什么第二个是 false 呢。
test()
其实如果我们仔细看过MDN上关于test的描述,应该就知道了:

关于这个lastIndex,很多小伙伴可能没有听过也没有用过,让我们看看下面的代码:
可以看到,reg 含有一个不可枚举的 lastIndex 属性。当然,如果你直接用 Object.keys(reg) 是看不到的:
(知识点:getOwnPropertyDescriptors() 和 keys() 的区别是什么呢。keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组。)
这个就是造成上面第二个是false的原因。因为每执行一次,其实我们的lastIndex都会变化:
根据上面的代码可以看到,这个lastIndex是会循环的。
所以第二次就出了问题。如果我们想让一开始的代码正常工作,该怎么办呢?
我们需要在每次执行后手动设置lastIndex。
test() 应用场景
我们举一个应用场景:后端传回来了一个图片 url 的数组,有 jpg、png 各种格式的,我们需要拿到以 jpg 结尾的图片:
首先,我们看看下面的代码:
(关于判断图片格式的这个正则大家不要纠结,其实是不完善的,比如url的名字是 ajpg.jpg 这种也可以匹配到,这个不重要,不是本文重点)


乍一看,觉得没毛病。我们看看结果:
WTF???
你肯定反应过来了,因为lastIndex变了,所以和预期不一致了,我们输出一下看看:
可以看到,true和false是依次输出的,所以我们没有拿到全部的jpg格式的url。
还有,就是如果当前匹配失败了,lastIndex也会重置为0,这一点也很重要,不然后面的就匹配不到了。
所以解决方法之一就是手动重置lastIndex:


其实,最好的方法就是直接去掉g,因为这种场景其实是不需要加上g的。这样lastIndex就一直是0,不会变了。结果也是符合预期的:
exec
此外,
lastIndex还和另一个正则方法exec()也是好兄弟,关系很近。用法和test()原理一样。很多人分不清
exec和match的关系,或者可能没有用过exec。match和exec的关系:首先,两个方法的来源是不一样的:
然后,不加g的时候是一样的:


然后,加了g的话:
match就没有捕获组了:
这个时候我们就需要用exec()方法了。
捕获组
关于捕获组,这里简单介绍一下。
捕获组分为四类:
捕获 ()
不捕获 (?:)
正向捕获 (?=)
反向捕获 (?!)
捕获组的功能很强大,这里就举两个例子:
例1:对日期进行任意格式的转换
例2: 千分位数字
因此,exec结合捕获组就可以实现很多复杂的逻辑。
exec() 应用场景
举个例子,我们要把一个字符串
a1b2c3d4e5里面的偶数的数字变成0。(方法肯定很多,这里只考虑用正则的方式)肯定有同学说直接用
replace就行了:(因为这里的转换条件比较简单,如果是很复杂的转换,比如我要把偶数的数字先转成ASCII码,然后反转呢?主要就是意思一下哈。)

我个人觉得exec比match强大的地方在于它可以深入到内部,暂停执行,类似 generate 函数,而不像match一样是个黑盒。
exec() 和 lastIndex
关于这个,其实业务中感觉用的不多,就举个例子吧:
代码大家肯定看得懂,我就不说了。
match是无法实现这样的功能的,因为匹配完就丢弃了。使用exec() 要注意的点就是不要写出死循环(毕竟有while)