在JavaScript正则表达式中命名捕获组?

据我所知,JavaScript中没有命名的捕获组。获得类似功能的替代方法是什么?

149855 次浏览

ECMAScript 2018在JavaScript正则表达式中引入了命名捕获组

例子:

  const auth = 'Bearer AUTHORIZATION_TOKEN'
const { groups: { token } } = /Bearer (?<token>[^ $]*)/.exec(auth)
console.log(token) // "AUTHORIZATION_TOKEN"

如果您需要支持旧的浏览器,您可以使用普通的(编号的)捕获组来完成您可以使用命名捕获组来完成的所有事情,您只需要跟踪数字—如果正则表达式中捕获组的顺序发生了变化,这可能会很麻烦。

只有两个“结构”;我能想到的命名捕获组的优点:

  1. 在某些正则表达式口味(。NET和JGSoft,据我所知),你可以在你的正则表达式(请看这里的例子,说明这一点很重要)中为不同的组使用相同的名称。但是大多数正则表达式都不支持这种功能。

  2. 如果您需要在被数字包围的情况下引用有编号的捕获组,则可能会遇到问题。假设你想给一个数字加一个0,因此想用$10替换(\d)。在JavaScript中,这是可行的(只要正则表达式中的捕获组少于10个),但是Perl会认为您正在寻找反向引用编号10而不是1,后面跟着一个0。在Perl中,在这种情况下可以使用${1}0

除此之外,命名的捕获组只是“语法糖”。只有在真正需要时才使用捕获组,并且在所有其他情况下使用非捕获组(?:...)会有所帮助。

JavaScript最大的问题(在我看来)是它不支持冗长的正则表达式,这使得创建可读的复杂正则表达式变得容易得多。

Steve Levithan的XRegExp库解决了这些问题。

为捕获的组命名有一个好处:减少与复杂正则表达式的混淆。

这真的取决于你的用例,但也许漂亮地打印你的正则表达式会有所帮助。

或者您可以尝试定义常量来引用您捕获的组。

注释可能还有助于向阅读您代码的其他人展示您所做的工作。

至于其他的,我必须同意蒂姆的回答。

你可以使用XRegExp,一个扩展的、可扩展的、跨浏览器的正则表达式实现,包括对额外语法、标志和方法的支持:

  • 增加了新的正则表达式和替换文本语法,包括对名叫捕获的全面支持。
  • 添加两个新的正则表达式标志:s,用于使点匹配所有字符(又名dotall或单行模式),x,用于自由空格和注释(又名扩展模式)。
  • 提供一套函数和方法,使复杂的正则表达式处理变得轻而易举。
  • 自动修复regex行为和语法中最常见的跨浏览器不一致。
  • 允许您轻松地创建和使用向XRegExp的正则表达式语言添加新语法和标志的插件。

有一个名为named-regexp的node.js库,你可以在你的node.js项目中使用它(在浏览器中通过使用browserify或其他打包脚本打包该库)。但是,该库不能用于包含未命名捕获组的正则表达式。

如果在正则表达式中计算开头捕获花括号,则可以在正则表达式中的命名捕获组和编号捕获组之间创建映射,并且可以自由混合和匹配。在使用正则表达式之前,只需删除组名。我写了三个函数来证明这一点。看这个要点:https://gist.github.com/gbirke/2cc2370135b665eee3ef

虽然你不能用普通JavaScript做到这一点,但也许你可以使用一些Array.prototype函数,比如Array.prototype.reduce,使用一些魔法将索引匹配转换为命名匹配。

显然,下面的解决方案需要匹配顺序:

.
// @text Contains the text to match
// @regex A regular expression object (f.e. /.+/)
// @matchNames An array of literal strings where each item
//             is the name of each group
function namedRegexMatch(text, regex, matchNames) {
var matches = regex.exec(text);


return matches.reduce(function(result, match, index) {
if (index > 0)
// This substraction is required because we count
// match indexes from 1, because 0 is the entire matched string
result[matchNames[index - 1]] = match;


return result;
}, {});
}


var myString = "Hello Alex, I am John";


var namedMatches = namedRegexMatch(
myString,
/Hello ([a-z]+), I am ([a-z]+)/i,
["firstPersonName", "secondPersonName"]
);


alert(JSON.stringify(namedMatches));

另一个可能的解决方案:创建一个包含组名和索引的对象。

var regex = new RegExp("(.*) (.*)");
var regexGroups = { FirstName: 1, LastName: 2 };

然后,使用对象键来引用组:

var m = regex.exec("John Smith");
var f = m[regexGroups.FirstName];

这可以提高使用正则表达式结果的代码的可读性/质量,但不会提高正则表达式本身的可读性。

在ES6中,你可以使用数组解构来捕获你的组:

let text = '27 months';
let regex = /(\d+)\s*(days?|months?|years?)/;
let [, count, unit] = regex.exec(text) || [];


// count === '27'
// unit === 'months'

注意:

  • 最后一个let中的第一个逗号跳过结果数组的第一个值,这是整个匹配的字符串
  • .exec()之后的|| []将在没有匹配时防止析构错误(因为.exec()将返回null)

更新:它终于成为JavaScript (ECMAScript 2018)!


命名捕获组可能很快就会加入JavaScript 提案已经进入第三阶段。 < / p > 捕获组可以使用(?<name>...)语法在角括号内指定名称,for 任何标识符名称。日期的正则表达式可以是 写为/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u。每个名称 必须是唯一的,并且遵循ECMAScript IdentifierName的语法 的groups属性中的属性可以访问命名组 正则表达式结果。对组的编号引用是 也创建了,就像未命名组一样。例如:< / p >
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';
正如蒂姆Pietzcker所说,ECMAScript 2018将命名捕获组引入JavaScript正则表达式。 但是我在上面的答案中没有发现的是如何在正则表达式本身中使用命名捕获组

你可以用这个语法使用命名捕获组:\k<name>。 例如< / p >

var regexObj = /(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>/

正如Forivin所说,你可以在对象结果中使用捕获组,如下所示:

let result = regexObj.exec('2019-28-06 year is 2019');
// result.groups.year === '2019';
// result.groups.month === '06';
// result.groups.day === '28';

  var regexObj = /(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>/mgi;


function check(){
var inp = document.getElementById("tinput").value;
let result = regexObj.exec(inp);
document.getElementById("year").innerHTML = result.groups.year;
document.getElementById("month").innerHTML = result.groups.month;
document.getElementById("day").innerHTML = result.groups.day;
}
td, th{
border: solid 2px #ccc;
}
<input id="tinput" type="text" value="2019-28-06 year is 2019"/>
<br/>
<br/>
<span>Pattern: "(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>";
<br/>
<br/>
<button onclick="check()">Check!</button>
<br/>
<br/>
<table>
<thead>
<tr>
<th>
<span>Year</span>
</th>
<th>
<span>Month</span>
</th>
<th>
<span>Day</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<span id="year"></span>
</td>
<td>
<span id="month"></span>
</td>
<td>
<span id="day"></span>
</td>
</tr>
</tbody>
</table>

没有ECMAScript 2018?

我的目标是使它的工作尽可能类似于我们所习惯的命名组。而在ECMAScript 2018中,你可以在组中放置?<groupname>来表示一个命名的组,在我的旧javascript解决方案中,你可以在组中放置(?!=<groupname>)来做同样的事情。所以它是一组额外的括号和一个额外的!=。很接近!

我把它都包装成一个字符串原型函数

特性

  • 适用于较旧的javascript
  • 没有额外的代码
  • 使用起来非常简单
  • Regex仍然有效
  • 组是在正则表达式本身中记录的
  • 组名可以有空格
  • 返回带有结果的对象

指令

  • (?!={groupname})放在你想要命名的每个组中
  • 记住,通过将?:放在组的开头来消除任何非捕获组()。这些不会被命名。

arrays.js

// @@pattern - includes injections of (?!={groupname}) for each group
// @@returns - an object with a property for each group having the group's match as the value
String.prototype.matchWithGroups = function (pattern) {
var matches = this.match(pattern);
return pattern
// get the pattern as a string
.toString()
// suss out the groups
.match(/<(.+?)>/g)
// remove the braces
.map(function(group) {
return group.match(/<(.+)>/)[1];
})
// create an object with a property for each group having the group's match as the value
.reduce(function(acc, curr, index, arr) {
acc[curr] = matches[index + 1];
return acc;
}, {});
};

使用

function testRegGroups() {
var s = '123 Main St';
var pattern = /((?!=<house number>)\d+)\s((?!=<street name>)\w+)\s((?!=<street type>)\w+)/;
var o = s.matchWithGroups(pattern); // {'house number':"123", 'street name':"Main", 'street type':"St"}
var j = JSON.stringify(o);
var housenum = o['house number']; // 123
}

o的结果

{
"house number": "123",
"street name": "Main",
"street type": "St"
}