2011-03-20 92 views
136

据我所知,在JavaScript中没有命名捕获组这样的事情。什么是获得类似功能的替代方法?在JavaScript正则表达式中命名捕获组?

+1

捕获组在JavaScript是由数.. $ 1是第一个捕捉组,$ 2,$ 3 ...高达$ 99,但它听起来就像你想要别的东西 - 不存在 – Erik 2011-03-20 08:09:50

+22

@Erik你在谈论_numbered_捕获组,OP在谈论_named_捕获组。它们存在,但我们想知道JS中是否支持它们。 – 2012-11-09 15:41:46

+3

有一个[将JavaScript命名为正则表达式的提议](https://github.com/littledan/es-regexp-named-groups),但是如果我们这样做的话,可能需要几年时间才能看到。 – 2016-10-11 05:12:25

回答

53

ECMAScript 2018将named capturing groups引入到JavaScript正则表达式中。

如果您需要支持较旧的浏览器,您可以使用正常(编号)的捕获组完成任何操作,您可以使用命名捕获组,只需要跟踪数字 - 如果在正则表达式变化中捕获组。

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

  1. 在一些正则表达式的口味(.NET和JGSoft,据我所知),您可以使用相同的正则表达式中的不同组的名称(see here for an example where this matters)。但大多数正则表达式并不支持这个功能。

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

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

更大的问题(在我看来)使用JavaScript的是,它不支持正则表达式冗长这将使可读的,复杂的正则表达式轻松很多的创作。

Steve Levithan's XRegExp library解决了这些问题。

+5

许多风格允许在正则表达式中多次使用相同的捕获组名称。但是,只有.NET和Perl 5.10+才能保持参与比赛的最后一组名称所捕获的值,因此特别有用。 – slevithan 2012-06-01 03:36:21

+3

+1为非捕获组 – Wulf 2012-08-25 09:50:46

+86

+1的巨大优势是:您可以更改您的RegExp,不需要数字到变量的映射。非捕获组解决了这个问题,除了一种情况:**如果组的顺序发生变化怎么办?**另外,如果把这些额外的字符放在其他组中...... – 2012-11-09 15:45:44

6

命名捕获组提供了一件事:减少混乱复杂的正则表达式。

这真的取决于你的用例,但也许漂亮 - 打印你的正则表达式可以帮助。

或者你可以尝试定义常量指捕获的组。然后

评论也可能有助于向人展示谁看了你的代码,你做了什么。

对于剩下的我必须蒂姆斯答案达成一致。

59

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

  • 添加新的正则表达式和替换文本语法,包括全面支持为named capture
  • 添加两个新的正则表达式标志:s,用于点匹配所有字符(也称为dotall或单线模式)和x,用于自由间距和注释(又名扩展模式)。
  • 提供一套功能和方法,使复杂的正则表达式处理变得轻而易举。
  • 自动修复了正则表达式行为和语法中最常遇到的跨浏览器不一致问题。
  • 让您轻松创建和使用插件,为XRegExp的正则表达式语言添加新的语法和标志。
+4

非常棒!非常感谢。 – 2012-03-31 20:24:42

4

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

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

+0

这是令人惊讶的轻量级,我会试试 – 2016-08-01 05:58:39

+0

它是否适用于复杂正则表达式中常规组内的嵌套命名组? – ElSajko 2016-10-14 22:18:16

+0

这并不完美。错误时:getMap(“((a | b(: c)))”); foo应该是第三组,而不是第二组。 /((a|b(c)))/g.exec("bc“); [“bc”,“bc”,“bc”,“c”] – ElSajko 2016-10-14 22:27:54

2

虽然你不能做到这一点香草的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));

+0

这很酷。我只是想..不可能创建一个接受自定义正则表达式的正则表达式函数吗?所以你可以像'var assocArray = Regex(“hello alex,我是dennis”,“hello({hisName}。+),我是({yourName}。+)”);' – Forivin 2015-08-29 16:46:52

+0

@Forivin显然你可以进一步发展这个功能。它不会很难得到它的工作:D – 2015-08-29 19:38:37

+0

您可以通过在其原型中添加一个函数来扩展“RegExp”对象。 – 2016-02-16 19:27:16

38

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

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

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

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

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

31

在ES6你可以使用数组解构赶上你的组:

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

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

注意:

  • 在最后let第一个逗号跳过结果数组的第一个值,也就是整个匹配字符串
  • || [].match()之后将防止出现解构错误(因为.match()将返回null
+1

第一个逗号是因为匹配返回的数组的第一个元素是输入表达式,对不对? – 2016-07-31 01:04:46

+1

'String.prototype.match'返回一个数组:位置0处的整个匹配字符串,然后是之后的任何组。第一个逗号说“跳过位置0的元素” – 2016-07-31 06:34:25

+1

我最喜欢的答案是用于那些有transpiling或ES6 +目标的人。这并不一定能够防止不一致性错误以及命名索引,例如,一个重用的正则表达式会发生变化,但我认为这里的简洁很容易弥补这一点。我在'String.prototype.match'上选择了'RegExp.prototype.exec'作为字符串可能为'null'或'undefined'的地方。 – 2017-07-31 15:29:15

2

命名捕获组可能会很快将其转换为JavaScript。
The proposal for it is at stage 3 already.

捕获组可以使用(?...))语法,对于 任何标识符名称。日期的正则表达式可以是 ,写为/(?\ d {4}) - (?\ d {2}) - (?\ d {2})/ u。每个名称 应该是唯一的,并遵循ECMAScript IdentifierName的语法。

命名组可以通过 的属性来访问正则表达式结果。与创建组的编号一样,也创建了 ,就像非命名组一样。例如:

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';