regex.test()返回结果异常,添加日志后代码恢复正常的问题排查
我需要从工作表提取的矩阵数据中,找出包含指定查询内容的所有行索引(查询内容可能在任意两列中)。本地用Quokka.js测试代码正常,但迁移到Apps Script IDE后发现regex.test()仅匹配部分符合条件的行;添加日志语句后代码却能正常工作,日志中还出现了矛盾的结果:第一次test()返回true,但紧接着第二次调用返回false。
数据示例
工作表数据:
| 查询1 | 查询2 | 标题 | 数量 |
|---|---|---|---|
| 0001 | 000A | Title 1 | 2 |
| 0001 | 000B | Title 2 | 4 |
| 0002 | 000C | Title 3 | 1 |
| 0003 | 000A | Title 4 | 2 |
| 0001 | 000D | Title 5 | 1 |
数据在代码中存储为矩阵orders:
[ [0001, '000A', 'Title 1', 2], [0001, '000B', 'Title 2', 4], ... ]
异常代码(仅匹配部分行)
// @param {Matrix} orders → row of matrix = [supplierID, tracking, ...others] // @param {String||Integer} query → supplier Id, tracking, etc. // @return {Array} instances → array of every row index that contains the query function searchOrdersMatrix(orders, query) { query = query.toString(); const regex = new RegExp(query, 'gi'); const instances = []; for (let n = 0; n < orders.length; ++n) { const row = orders[n].join(''); const rowMatches = regex.test(row); if (rowMatches) instances.push(n + 2); } return instances; }
正常工作的代码(添加日志后)
// @param {Matrix} orders → row of matrix = [supplierID, tracking, ...others] // @param {String||Integer} query → supplier Id, tracking, etc. // @return {Array} instances → array of every row index that contains the query function searchOrdersMatrix(orders, query) { query = query.toString(); const regex = new RegExp(query, 'gi'); const instances = []; for (let n = 0; n < orders.length; n++) { const row = orders[n].join(''); const rowMatches = regex.test(row); if (rowMatches) Logger.log(`Row ${n+2} Regex result: ${regex.test(row)}`); if (rowMatches) { instances.push(n + 2); Logger.log(`Pushed row ${n + 2}`) } } return instances; }
核心疑问
- 第一段代码仅能匹配部分包含查询内容的行;
- 第二段代码添加日志语句后能匹配所有符合条件的行,但移除日志语句后又回到第一段代码的异常状态;
- 日志显示矛盾结果:
rowMatches为true,但日志中第二次调用regex.test(row)却返回false,例如:
行2 正则结果: false,
已添加行2
行3 正则结果: false,
已添加行3
行5 正则结果: false,
已添加行5
问题原因:RegExp的lastIndex属性导致的匹配偏移
这是因为带有g(全局匹配)标志的正则表达式对象是有状态的:每次调用test()方法后,正则对象的lastIndex属性会被更新为当前匹配结果的结束位置,下一次调用test()时会从这个位置开始查找。
具体分析:
第一段代码的异常:
循环中重复调用同一个正则对象的test()方法。第一次匹配行时,lastIndex被设为匹配结束的位置;下一行调用test()时,会从这个lastIndex位置开始查找当前行的字符串,而不是从开头。如果当前行的匹配内容在lastIndex之前,就会返回false,导致漏匹配。第二段代码“正常”的原因:
当rowMatches为true时,日志中额外调用了一次regex.test(row),这次调用会把lastIndex重置为0(因为第二次在同一行调用test(),找不到新的匹配后,lastIndex会被重置)。这样下一次循环处理新行时,test()会从字符串开头开始查找,从而正确匹配。日志中的矛盾结果:
第一次regex.test(row)返回true,lastIndex被设为匹配结束位置;紧接着第二次调用regex.test(row)时,从lastIndex位置开始查找同一行,找不到新的匹配,所以返回false,同时lastIndex重置为0。
解决方案
解决方法有三种:
- 移除全局标志
g:如果不需要全局匹配(这里只是检查行中是否存在查询内容,不需要多次匹配),去掉g标志,正则对象就不会维护lastIndex状态:const regex = new RegExp(query, 'i'); // 仅保留忽略大小写的i标志 - 每次循环创建新的正则对象:在循环内部创建正则,确保每次匹配都从字符串开头开始:
for (let n = 0; n < orders.length; ++n) { const row = orders[n].join(''); const regex = new RegExp(query, 'gi'); // 循环内创建新正则 const rowMatches = regex.test(row); if (rowMatches) instances.push(n + 2); } - 手动重置
lastIndex:每次调用test()前,把regex.lastIndex设为0:for (let n = 0; n < orders.length; ++n) { const row = orders[n].join(''); regex.lastIndex = 0; // 重置匹配位置 const rowMatches = regex.test(row); if (rowMatches) instances.push(n + 2); }
内容的提问来源于stack exchange,提问作者kanwoody

