查看原文
其他

重复命名捕获组,你学废了吗

爱前端的Q 前端F2E
2024-08-30

新特性:重复命名捕获组

大家好!今天我们来聊聊 ECMAScript 2025 带来的一个新特性——“重复命名捕获组”。这个特性是由 Kevin Gibbons 提出的,它让我们能够在正则表达式中多次使用同一个捕获组的名字。

重复命名捕获组

通常,我们不允许捕获组的名字重复,因为一个捕获组只能有一个值,如果有多个同名的捕获组,它就不知道该抓哪个了。比如下面这样:

/^(?<x>a)(?<x>b)$/

这段代码会抛出一个错误,因为捕获组 x 可以捕获 'a''b',但它不能同时捕获两个。

但是,如果同名的捕获组出现在不同的选择分支中,那就没有冲突了。之前这是不允许的,但现在可以了。比如:

/^((?<x>a)|(?<x>b))$/.exec('a').groups
// { x: 'a' }
/^((?<x>a)|(?<x>b))$/.exec('b').groups
// { x: 'b' }

这有什么用呢?它让我们能够在不同的选择分支之间重用正则表达式的片段和匹配处理代码。

接下来,我们通过一些例子来看看它的实际应用。

具有相似部分的替代格式

parseMonth() 函数使用正则表达式来解析具有两种不同月格式的字符串:

const RE_MONTH = new RegExp(
  `^(${'year=\\\\d{4}-'}month=\\\\d{2}}|${'month=\\\\d{2}\\\\/'}year=\\\\d{4}})$`
);

function parseMonth(monthStr) {
  const match = RE_MONTH.exec(monthStr);
  if (match === null) {
    throw new Error('这不是一个有效的月份字符串:' + JSON.stringify(monthStr));
  }
  // 两种情况都用同一段代码
  return {
    year: match.groups.year,
    month: match.groups.month,
  };
}

console.log(parseMonth('2024-05'));
// { year: '2024', month: '05' }
console.log(parseMonth('05/2024'));
// { year: '2024', month: '05' }
assert.throws(() => parseMonth('2024/05'));

重用正则表达式片段

下面的代码展示了如何通过重复命名捕获组来重用正则表达式的片段——在这个例子中是 KEYVALUE

const KEY = `(?<key>[a-z]+)`;
const VALUE = `(?<value>[a-z]+)`;
const RE_KEY_VALUE_PAIRS = new RegExp(
  `\\\\(${KEY}=${VALUE}\\\\)|\\\\[${KEY}:${VALUE}\\\\]`,
  'g'
);

const str = '[one:a] (two=b)';
const objects = Array.from(
  str.matchAll(RE_KEY_VALUE_PAIRS),
  // 两种情况都用同一段代码
  (match) => ({key: match.groups.key, value: match.groups.value})
);
console.log(objects);
// [
//   { key: 'one', value: 'a' },
//   { key: 'two', value: 'b' },
// ]

解释

  • string.matchAll() 返回一个可迭代对象。
  • 我们使用 Array.from() 将这个可迭代对象转换为数组。
  • Array.from() 的第二个可选参数是一个回调函数,它在元素放入返回的数组之前被应用。可以想象成 array.map()

反向引用

对重复命名组的反向引用按预期工作。下面的例子有些牵强(因为我们本可以使用单个命名组),但它展示了可能性:

const RE_DELIMITED = /^((?<delim>\\_)|(?<delim>\\*))[a-z]+\\k<delim>$/;
console.log(RE_DELIMITED.test('_abc_')); // true
console.log(RE_DELIMITED.test('*abc*')); // true
console.log(RE_DELIMITED.test('_abc*')); // false
console.log(RE_DELIMITED.test('*abc_')); // false

支持的 JavaScript 引擎

  • 该提案维护了一个引擎列表[1],这些引擎已经支持重复命名捕获组。
  • 我使用 Markcheck[2] 测试了这篇博客文章中的代码,并使用了一个 Babel 插件[3](因为 Node 的 V8 还不支持这个特性)。
    • 注意:它只透明支持正则表达式字面量;我使用了一个 变通方法[4]

结论

在实践中,重复命名捕获组对于那些基于正则表达式的解析器和分词器的编写者来说最有用。对这些人来说,这是一个非常受欢迎的补充。

本文译自:https://2ality.com/2024/05/proposal-duplicate-named-capturing-groups.html

Reference
[1]

引擎列表: https://github.com/tc39/proposal-duplicate-named-capturing-groups/issues/4

[2]

Markcheck: https://github.com/rauschma/markcheck

[3]

Babel 插件: https://babeljs.io/docs/babel-plugin-proposal-duplicate-named-capturing-groups-regex

[4]

变通方法: https://github.com/babel/babel/issues/16502


继续滑动看下一个
前端F2E
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存