重復命名捕獲組,你學廢了嗎?
新特性:重復命名捕獲組
大家好!今天我們來聊聊 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'));
重用正則表達式片段
下面的代碼展示了如何通過重復命名捕獲組來重用正則表達式的片段——在這個例子中是 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() 返回一個可迭代對象。
- 我們使用 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