JavaScript 正则匹配区间:相同文字出现两次,怎样标中真正捕获的那一段

10-01 3阅读

解析日志后要高亮一个数字,代码先拿到捕获文本,再调用 indexOf 搜索它的位置。如果同一行里相同数字出现两次,就可能标错第一处。正则实际上已经知道自己匹配了哪一段,启用 d 标志便能把这份位置一并取回。

匹配区间以起点与终点组成,起点包含在内,终点不包含在内,可以直接交给原字符串的 slice。命名捕获组还提供按名称读取的区间,减少依赖第几个括号的脆弱关联。我们用重复数字做一个可验证样本。

位置必须回到本次匹配的原文

保存以下内容为 demo.mjs,再运行 node demo.mjs。本文在 Node.js v24.19.0 验证。样本开头放了一个由两个 UTF-16 码元表示的符号,同时保留两处相同数字,以检查位置单位与匹配来源。

JavaScript 正则匹配区间:相同文字出现两次,怎样标中真正捕获的那一段

AI概念示意图:表现本文的抽象关系,并非软件截图或真实运行结果。

import assert from 'node:assert/strict';
const text = '😀 old=12 new=12';
const found = /new=(?<value>\d+)(?:\.(?<fraction>\d+))?/d.exec(text);
assert.ok(found);
assert.deepEqual(found.indices[0], [10, 16]);
assert.deepEqual(found.indices.groups.value, [14, 16]);
assert.equal(found.indices.groups.fraction, undefined);
const [start, end] = found.indices.groups.value;
assert.equal(text.slice(start, end), found.groups.value);
assert.equal(text.indexOf(found.groups.value), 7);
assert.notEqual(text.indexOf(found.groups.value), start);
const unicode = /new=(?<value>\d+)/du.exec(text);
assert.deepEqual(unicode.indices.groups.value, [14, 16]);
const repeated = /(?<last>\d)+/d.exec('123');
assert.equal(repeated.groups.last, '3');
assert.deepEqual(repeated.indices.groups.last, [2, 3]);
assert.equal(/x/d.exec(text), null);
console.log(`full=${found.indices[0]} value=${start},${end}`);
console.log('capture-indices checks passed');

捕获文字相同,来源位置仍然不同

完整匹配从十到十六,value 的区间从十四到十六。直接切片得到十二,恰好等于捕获文本。indexOf 却返回七,因为它找到的是 old 后面的第一次出现。两种调用都正确,只是回答的问题不同。

因此,做错误定位、源码标注或日志高亮时,应保存正则返回的区间与对应原文。不要丢掉位置后再靠文字重搜,也不要在匹配之后先去空格、换行或规范化文本,再把旧区间套到改过的字符串上。

可选的 fraction 没有参与匹配,所以它的区间为 undefined。代码没有直接解构这个值,避免把“组存在但本次未匹配”误当成一个空区间。若整个表达式没有匹配成功,exec 返回的则是 null,需要更早检查。

d 给位置,u 不会把位置改成字素编号

样本开头的符号占两个码元,所以数字位置不能简单按肉眼看到的字符数来数。第二个表达式加入 u 标志后,value 的区间仍是十四到十六。这些索引适配 JavaScript 字符串切片,不是文件的 UTF-8 字节偏移,也不是界面上的第几个可见字。

如果要将区间发送给按字节定位的编辑器接口,必须明确编码并进行转换。反过来,如果只是同一个字符串的 slice,就不应先把索引换成码点计数,否则容易产生第二次偏移错误。选择位置单位应由使用接口决定。

重复捕获的例子匹配一二三,但 last 只留下最后一次捕获的三,区间也是二到三。indices 并不会自动提供循环中每轮捕获的历史。若需要每个数字的位置,应改变匹配策略,让每一次匹配本身成为独立结果。

本文的高亮只验证位置,没有拼接 HTML。实际展示原始文本时,应由安全的文本节点或合适编码器输出内容。区间计算正确与网页输出正确是两件事,前者不能替后者处理标签与特殊字符。

给标注功能写回归样本时,可同时保留重复文本、未参与匹配的可选组、开头的多码元符号和整体不匹配四种情况。每个已匹配组都验证“原文按区间切片等于捕获值”,这比手写一个看似合理的位置数字更能发现偏移来源。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。