For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/year/2018/proposal-regexp-named-groups.md.
  • 简体中文
  • RegExp named capture groups S4

    中文标题:正则表达式命名捕获组

    提案概览
    提案速览

    该提案通过 (?.) 语法为 JavaScript 正则表达式添加了命名捕获组,允许在匹配结果、反向引用 (\k) 和替换字符串 ($) 中按名称引用组。它解决了依赖数字组索引的 regex 难以阅读和重构的问题。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    正则表达式命名捕获组

    阶段 4

    提案发起人:Daniel Ehrenberg (Igalia) & Mathias Bynens (Google)

    简介

    编号捕获组允许人们引用正则表达式匹配的字符串的某些部分。每个捕获组被分配一个唯一的号码,并且可以通过该号码进行引用,但这可能使正则表达式难以理解和重构。

    例如,给定匹配日期的 /(\d{4})-(\d{2})-(\d{2})/,如果不检查周围的代码,就无法确定哪个组对应月份,哪个组对应日。此外,如果要交换月份和日的顺序,组引用也必须更新。

    命名捕获组为这些问题提供了一个很好的解决方案。

    高层 API

    捕获组可以使用 (?<name>...) 语法为任何标识符 name 命名。日期的正则表达式可以写成 /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u。每个名称应该唯一,并遵循 ECMAScript IdentifierName 的语法。

    命名组可以从正则表达式结果的 groups 属性的属性中访问。也会创建对组的编号引用,就像对待非命名组一样。例如:

    let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
    let result = re.exec('2015-01-02');
    // result.groups.year === '2015';
    // result.groups.month === '01';
    // result.groups.day === '02';
    
    // result[0] === '2015-01-02';
    // result[1] === '2015';
    // result[2] === '01';
    // result[3] === '02';

    该接口与解构配合得很好,如下例所示:

    let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
    console.log(`one: ${one}, two: ${two}`);  // 打印 one: foo, two: bar

    反向引用

    命名组可以在正则表达式内通过 \k<name> 构造访问。例如,

    let duplicate = /^(?<half>.*).\k<half>$/u;
    duplicate.test('a*b'); // false
    duplicate.test('a*a'); // true

    命名引用也可以与编号引用同时使用。

    let triplicate = /^(?<part>.*).\k<part>.\1$/u;
    triplicate.test('a*a*a'); // true
    triplicate.test('a*a*b'); // false

    替换目标

    命名组也可以从传给 String.prototype.replace 的替换值中引用。如果该值是字符串,可以使用 $<name> 语法访问命名组。例如:

    let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
    let result = '2015-01-02'.replace(re, '$<day>/$<month>/$<year>');
    // result === '02/01/2015'

    请注意,传给 replace 的是普通字符串字面量,而不是模板字面量,因为该方法会解析 day 等的值,而不是将它们作为局部变量。另一种选择是使用 ${day} 语法(同时保持不是模板字符串);本提案使用 $<day> 是为了与组的定义相呼应,并与模板字面量区分开来。

    如果 String.prototype.replace 的第二个参数是函数,则命名组可以通过名为 groups 的新参数访问。新签名将是 function (matched, capture1, ..., captureN, position, S, groups)。命名捕获组仍然像往常一样参与编号。例如:

    let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
    let result = '2015-01-02'.replace(re, (...args) => {
     let {day, month, year} = args[args.length - 1];
     return `${day}/${month}/${year}`;
    });
    // result === '02/01/2015'

    细节

    重叠的组名

    RegExp 结果对象已经有了一些非数字属性,命名捕获组可能与这些属性重叠,即 lengthindexinput。在本提案中,为了避免重叠名称带来的歧义和边缘情况,命名组属性被放置在单独的 groups 对象上,该对象是匹配对象的属性。这个解决方案将允许在未来的 ECMAScript 版本中向 exec 的结果添加额外的属性,而不会产生任何 Web 兼容性风险。

    仅对具有命名组的 RegExp 创建 groups 对象。它不包含编号组属性,只包含命名组属性。对于 RegExp 中提到的所有组,都会在 groups 对象上创建属性;如果在匹配中未遇到这些组,则值为 undefined

    新语法向后兼容性

    创建新命名组的语法 /(?<name>)/ 目前在任何 ECMAScript 正则表达式中都是语法错误,因此可以添加到所有正则表达式中而不会产生歧义。但是,命名反向引用语法 /\k<foo>/ 目前在非 Unicode 正则表达式中是允许的,并且匹配字面字符串 "k<foo>"。在 Unicode 正则表达式中,这种转义是被禁止的。

    在本提案中,非 Unicode 正则表达式中的 \k<foo> 将继续匹配字面字符串 "k<foo>"除非正则表达式包含命名组,在这种情况下,它将匹配该组或成为语法错误,具体取决于正则表达式是否有一个名为 foo 的命名组。这不影响现有代码,因为目前没有有效的正则表达式可以包含命名组。这将是一个重构风险,尽管仅对于在正则表达式中包含 \k 的代码。

    其他编程语言中的先例

    本提案类似于许多其他编程语言对命名捕获组的处理方式。这似乎是共识语法的发展方向,尽管 Python 的语法是一个有趣且引人注目的例外,它可以解决非 Unicode 反向引用问题。

    Perl 参考

    Perl 使用与本提案相同的语法来处理命名捕获组 /(?<name>)/ 和反向引用 /\k<name>/

    Python 参考

    命名捕获组使用语法 "(?P<name>)",并有反向引用 (?P=name)

    Java 参考

    JDK7+ 支持与 Perl 和本提案类似的语法来命名捕获组。

    .NET 参考

    C# 和 VB.NET 支持使用语法 "(?<name>)" 以及 "(?'name')" 来命名捕获组,并使用 "\k<name>" 进行反向引用。

    PHP

    根据 Stack Overflow 帖子php.net 文档上的评论,PHP 长期以来一直支持使用语法 "(?P<foo>)" 的命名组,该语法可作为结果匹配对象的属性使用。

    Ruby 参考

    Ruby 的语法与 .NET 相同,支持使用语法 "(?<name>)" 以及 "(?'name')" 来命名捕获组,并使用 "\k<name>" 进行反向引用。

    规范草案

    草案规范

    实现