重復(fù)命名捕獲組,你學(xué)廢了嗎?
新特性:重復(fù)命名捕獲組
大家好!今天我們來(lái)聊聊 ECMAScript 2025 帶來(lái)的一個(gè)新特性——“重復(fù)命名捕獲組”。這個(gè)特性是由 Kevin Gibbons 提出的,它讓我們能夠在正則表達(dá)式中多次使用同一個(gè)捕獲組的名字。
重復(fù)命名捕獲組
通常,我們不允許捕獲組的名字重復(fù),因?yàn)橐粋€(gè)捕獲組只能有一個(gè)值,如果有多個(gè)同名的捕獲組,它就不知道該抓哪個(gè)了。比如下面這樣:
/^(?<x>a)(?<x>b)$/
這段代碼會(huì)拋出一個(gè)錯(cuò)誤,因?yàn)椴东@組 x 可以捕獲 'a' 或 'b',但它不能同時(shí)捕獲兩個(gè)。
但是,如果同名的捕獲組出現(xiàn)在不同的選擇分支中,那就沒(méi)有沖突了。之前這是不允許的,但現(xiàn)在可以了。比如:
/^((?<x>a)|(?<x>b))$/.exec('a').groups
// { x: 'a' }
/^((?<x>a)|(?<x>b))$/.exec('b').groups
// { x: 'b' }
這有什么用呢?它讓我們能夠在不同的選擇分支之間重用正則表達(dá)式的片段和匹配處理代碼。
接下來(lái),我們通過(guò)一些例子來(lái)看看它的實(shí)際應(yīng)用。
具有相似部分的替代格式
parseMonth() 函數(shù)使用正則表達(dá)式來(lái)解析具有兩種不同月格式的字符串:
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('這不是一個(gè)有效的月份字符串:' + 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'));
重用正則表達(dá)式片段
下面的代碼展示了如何通過(guò)重復(fù)命名捕獲組來(lái)重用正則表達(dá)式的片段——在這個(gè)例子中是 KEY 和 VALUE。
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() 返回一個(gè)可迭代對(duì)象。
- 我們使用 Array.from() 將這個(gè)可迭代對(duì)象轉(zhuǎn)換為數(shù)組。
- Array.from() 的第二個(gè)可選參數(shù)是一個(gè)回調(diào)函數(shù),它在元素放入返回的數(shù)組之前被應(yīng)用??梢韵胂蟪?nbsp;array.map()。
反向引用
對(duì)重復(fù)命名組的反向引用按預(yù)期工作。下面的例子有些牽強(qiáng)(因?yàn)槲覀儽究梢允褂脝蝹€(gè)命名組),但它展示了可能性:
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 引擎
- 該提案維護(hù)了一個(gè)引擎列表[1],這些引擎已經(jīng)支持重復(fù)命名捕獲組。
- 我使用 Markcheck[2] 測(cè)試了這篇博客文章中的代碼,并使用了一個(gè) Babel 插件[3](因?yàn)?Node 的 V8 還不支持這個(gè)特性)。
- 注意:它只透明支持正則表達(dá)式字面量;我使用了一個(gè) 變通方法[4]。
結(jié)論
在實(shí)踐中,重復(fù)命名捕獲組對(duì)于那些基于正則表達(dá)式的解析器和分詞器的編寫者來(lái)說(shuō)最有用。對(duì)這些人來(lái)說(shuō),這是一個(gè)非常受歡迎的補(bǔ)充。
本文譯自:https://2ality.com/2024/05/proposal-duplicate-named-capturing-groups.html