据我所知,JavaScript中没有所谓的捕获组。获得类似功能的替代方法是什么?
据我所知,JavaScript中没有所谓的捕获组。获得类似功能的替代方法是什么?
Answers:
ECMAScript 2018将命名捕获组引入了JavaScript正则表达式中。
例:
const auth = 'Bearer AUTHORIZATION_TOKEN'
const { groups: { token } } = /Bearer (?<token>[^ $]*)/.exec(auth)
console.log(token) // "Prints AUTHORIZATION_TOKEN"
如果您需要支持较旧的浏览器,则可以使用常规(编号)捕获组来完成所有操作,就像使用命名捕获组一样,您只需要跟踪数字即可-如果捕获组中的捕获组顺序很麻烦,正则表达式更改。
我可以想到的命名捕获组只有两个“结构”优点:
在某些正则表达式中(据我所知,.NET和JGSoft),您可以对正则表达式中的不同组使用相同的名称(请参阅此处的示例)。但是无论如何大多数正则表达式版本都不支持此功能。
如果在数字包围的情况下需要引用编号的捕获组,则会遇到问题。假设您要在数字上添加零,因此要替换(\d)
为$10
。在JavaScript中,这将起作用(只要您的正则表达式中的捕获组少于10个),但是Perl会认为您正在寻找的是反向引用编号,10
而不是number 1
,后跟一个0
。在Perl中,您可以${1}0
在这种情况下使用。
除此之外,命名的捕获组只是“语法糖”。仅当您确实需要捕获组时才使用捕获组,并(?:...)
在所有其他情况下使用非捕获组会有所帮助。
JavaScript的更大问题(在我看来)是它不支持冗长的正则表达式,这会使创建可读的,复杂的正则表达式更加容易。
Steve Levithan的XRegExp库解决了这些问题。
您可以使用XRegExp,它是正则表达式的增强,可扩展,跨浏览器的实现,包括对其他语法,标志和方法的支持:
s
,使点与所有字符匹配(又称为点号或单行模式);以及x
,使自由间距和注释(又称为扩展模式)。在ES6中,可以使用数组解构来捕获组:
let text = '27 months';
let regex = /(\d+)\s*(days?|months?|years?)/;
let [, count, unit] = regex.exec(text) || [];
// count === '27'
// unit === 'months'
注意:
let
跳过结果数组的第一个值,即整个匹配的字符串|| []
after .exec()
将防止销毁错误(因为.exec()
将返回null
)String.prototype.match
返回以下数组:整个匹配的字符串位于位置0,然后是其后的任何组。第一个逗号说“在位置0跳过元素”
RegExp.prototype.exec
结束。String.prototype.match
null
undefined
更新:终于将其纳入JavaScript(ECMAScript 2018)!
命名捕获组可以很快将其纳入JavaScript。
提案已经在第三阶段。
(?<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 {year, month, day} = ((result) => ((result) ? result.groups : {}))(re.exec('2015-01-02'));
您可以在node.js项目中使用一个名为named-regexp的node.js库(在浏览器中,通过使用browserify或其他打包脚本打包该库)。但是,该库不能与包含未命名捕获组的正则表达式一起使用。
如果您在正则表达式中计算开头的捕获括号,则可以在正则表达式中的命名捕获组和编号的捕获组之间创建映射,并且可以自由地混合和匹配。您只需要在使用正则表达式之前删除组名即可。我编写了三个函数来演示这一点。看到这个要点:https : //gist.github.com/gbirke/2cc2370135b665eee3ef
正如Tim Pietzcker所说的那样,ECMAScript 2018将命名捕获组引入了JavaScript正则表达式中。但是我在上面的答案中找不到的是如何在正则表达式中使用命名的捕获组。
您可以通过以下语法使用命名的捕获组:\k<name>
。例如
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>
虽然您无法使用普通JavaScript做到这一点,但也许您可以使用一些Array.prototype
函数,例如Array.prototype.reduce
使用某些magic将索引的匹配项转换为已命名的匹配项。
显然,以下解决方案将需要按顺序进行匹配:
// @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 assocArray = Regex("hello alex, I am dennis", "hello ({hisName}.+), I am ({yourName}.+)");
RegExp
通过向其原型添加函数来扩展对象。
没有ECMAScript 2018吗?
我的目标是使其工作尽可能类似于命名组所惯用的工作。在ECMAScript 2018中,您可以将其?<groupname>
放置在组中以指示已命名的组,而在我的旧javascript解决方案中,您可以将其(?!=<groupname>)
放置在组中以执行相同的操作。因此,这是一组额外的括号和一个额外的!=
。八九不离十!
我将所有内容包装到一个字符串原型函数中
特征
使用说明
(?!={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"
}