正则 lastIndex 状态坑
带 g 或 y 标志的正则有状态,反复调用 test/exec 时受 lastIndex 影响,导致"第一次 true 第二次 false"的反直觉行为。
#type / concept
#status / evergreen
#resource / javascript
#resource / ecmascript
[!info] related notes
- 所属 MOC: ecmascript-moc
- 前置概念: ecmascript-regular-expressions
- 易混淆: 贪婪/非贪婪与状态无关,见 regex-greedy-vs-non-greedy
正则 lastIndex 状态坑
带 g 或 y 标志的正则表达式是有状态的——它会记住上一次匹配结束的位置,存储在 lastIndex 属性里,下一次从那里继续找。
现象
const reg = /\d/g;
console.log(reg.test("1")); // true
console.log(reg.test("1")); // false ← 反直觉
不是正则写错了,是 lastIndex 在移动:
const reg = /\d/g;
console.log(reg.lastIndex); // 0
console.log(reg.test("1")); // true
console.log(reg.lastIndex); // 1
console.log(reg.test("1")); // false(从 index 1 开始,找不到了)
console.log(reg.lastIndex); // 0(找不到后自动重置)
原因
- 不带
g/y的正则没有状态,每次从头开始,不会有这个问题 - 带
g/y的正则每次匹配后会更新lastIndex - 找不到时
lastIndex重置为 0,所以下一次又正常了
常见触发场景
1. 在循环条件里用 test()
const reg = /\d+/g;
while (reg.test("a1b2c3")) {
// 第一次 true,匹配 "1"
// 第二次 true,匹配 "2"
// 第三次 true,匹配 "3"
// 第四次 false,退出
// 看起来正常,但语义上容易搞混
}
2. 在 if 里复用同一个正则对象
const reg = /\d+/g;
if (reg.test("abc123")) {
console.log("有数字");
}
if (reg.test("abc456")) {
console.log("也有数字"); // 可能不执行!取决于上一次的 lastIndex
}
3. 函数里使用外部正则
const numberReg = /\d+/g;
function hasNumber(str) {
return numberReg.test(str); // 危险:外部状态影响结果
}
console.log(hasNumber("abc1")); // true
console.log(hasNumber("abc1")); // 可能 false
解法
方案 A:不要给判断类操作加 g
如果只是判断”有没有匹配”,通常不需要 g:
const reg = /\d+/; // 不带 g
reg.test("abc123"); // 始终 true
reg.test("abc123"); // 始终 true
方案 B:每次使用前重置 lastIndex
const reg = /\d+/g;
reg.lastIndex = 0;
reg.test("abc123");
方案 C:函数内创建新的正则
function hasNumber(str) {
return /\d+/g.test(str); // 每次新对象,无状态问题
}
方案 D:用 match() / search() 替代
这些方法每次从头开始,不受 lastIndex 影响:
"abc123".match(/\d+/); // 始终返回结果
"abc123".search(/\d+/); // 始终返回索引
总结
| 操作 | 建议 |
|---|---|
| 判断是否匹配 | 用 test() 时不要加 g |
| 提取所有匹配 | 用 match(/…/g) 或 matchAll() |
| 循环提取详情 | 用 exec() + g,但理解 lastIndex 机制 |
| 复用正则对象 | 每次用前 reg.lastIndex = 0 |