正则匹配不准确问题
复现场景(建议可以打开控制台试下):
let reg = /J/gi
for (let i = 0; i <= 10; i++) {
console.log(reg.test('Jioho'))
}
// true 和 false 交替输出
是不是认为是循环的锅?来个非循环的场景
let reg = /i/gi
console.log(reg.test('Jioho')) // true
console.log(reg.test('Jioho')) // false
console.log(reg.test('Jioho')) // true
console.log(reg.test('Jioho')) // false
console.log(reg.test('Jioho')) // true
为什么正则第二次匹配无效
是因为正则表达式中的 g
,循环匹配的问题
正则的 lastIndex 从字面上来讲就是最后一个索引,实际上它的意思是正则表达式开始
下一次查找的索引位置
,第一次的时候总是为 0 的,第一次查找完了的时候会把 lastIndex 的值设为匹配到得字符串的最后一个字符的索引位置加 1,第二次查找的时候会从 lastIndex 这个位置开始,后面的以此类推。如果没有找到,则会把 lastIndex 重置为 0
。要注意的是,lastIndex 属性只有在有全局标志正则表达式中才有作用
所以上面的例子中 reg
只声明了一次,可是使用了多次所以才出现那种情况
如果代码改成这样,输出就符合预期了:
// 1. 去除 g
let reg = /j/i
console.log(reg.test('Jioho'))
console.log(reg.test('Jioho'))
console.log(reg.test('Jioho'))
// 2. 循环中重新创建正则
for (let i = 0; i <= 10; i++) {
const reg = /j/gi
console.log(reg.test('Jioho'))
}
深入了解正则 Regex
在 MDN 文档有详细的介绍:RegExp(正则表达式)
看到今天的主角:RegExp.lastIndex
lastIndex
是正则表达式的一个可读可写的整型属性,用来指定下一次匹配的起始索引。
注意是可读可写的。而且查看详细的介绍规则:
只有正则表达式使用了表示全局检索的 “g” 标志时,该属性才会起作用。此时应用下面的规则:
- 如果 lastIndex 大于字符串的长度,则 regexp.test 和 regexp.exec 将会匹配失败,然后 lastIndex 被设置为 0。
- 如果 lastIndex 等于字符串的长度,且该正则表达式匹配空字符串,则该正则表达式匹配从 lastIndex 开始的字符串。(then the regular expression matches input starting at lastIndex.)
- 如果 lastIndex 等于字符串的长度,且该正则表达式不匹配空字符串 ,则该正则表达式不匹配字符串,lastIndex 被设置为 0.。
- 否则,lastIndex 被设置为紧随最近一次成功匹配的下一个位置。
文档中还介绍到了一个 RegExp.prototype.compile()
运行脚本的期间(重新)编译正则表达式。
所以我们可以在循环中或者在特定业务逻辑下,重新编译正则表达式
最后
关于正则多次匹配无效的问题可能的原因就找到了,因为使用了 g
循环匹配的标记,而且正则在使用一次后没有重新编译,可是之前有已经有相关的匹配了,lastInedx
不为 0
要解决这种问题方法也很多
- 每次都重新声明正则表达式
for (let i = 0; i <= 10; i++) {
const reg = /j/gi
console.log(reg.test('Jioho'))
}
- 重置 lastIndex
const reg = /j/gi
for (let i = 0; i <= 10; i++) {
reg.lastIndex = 0
console.log(reg.test('Jioho'))
}
- 重新编译正则
const reg = /j/gi
for (let i = 0; i <= 10; i++) {
reg.compile()
console.log(reg.test('Jioho'))
}