正则表达式是前端开发中不可或缺的工具之一。在 ECMAScript 2018 中,引入了一项新的特性——“命名捕获组(named capture groups)”,在处理正则表达式匹配时可以大大提高代码的可读性和可维护性。
在此我们来详细地探讨这个新特性,并给出一些实用的示例代码,帮助大家更好地理解和应用它。
什么是命名捕获组
我们知道,在正则表达式中,使用括号((
和)
)将某一部分子表达式括起来,作为一个“捕获组(capture group)”,它能够捕获和匹配符合条件的字符串,方便后续处理。而在 ECMAScript 2018 中,我们可以给这些捕获组起一个名字,方便在后续代码中直接引用这个组,而不必使用索引。
例如,下面是一个匹配日期的正则表达式,其中包含了三个捕获组:
const reg = /(\d{4})-(\d{2})-(\d{2})/; const str = '2019-06-10'; const [matched, year, month, day] = str.match(reg); console.log(year, month, day); // '2019', '06', '10'
在上面的代码中,我们使用括号将年、月、日三个部分括起来,称为“捕获组”,然后使用数组的解构语法,将这三个捕获组分别赋值给了变量year
、month
和day
。
然而,若是你的正则表达式更为复杂,包含多个捕获组,那么就会出现这样的情况:
const reg = /^(\w+)\s+(\d+),\s+(\d+)\s+(\d+):(\d+)\s+(\w+)$/; const str = 'Tue May 20, 2019 16:30 GMT'; const [matched, month, day, year, hour, minute, timezone] = str.match(reg); console.log(month, day, year, hour, minute, timezone); // 'Tue', 'May', '20', '2019', '16', 'GMT'
在上面这个例子中,我们需要根据一大堆数字和字母的组合,手动去分配各个捕获组的值,使得代码变得冗长而不易读。
这时,命名捕获组就可以为我们解决这个问题。我们只需在括号内给捕获组起一个名字,就可以在解构语法中直接使用这个名字,而不必再去使用索引。
const reg = /^(?<month>\w+)\s+(?<day>\d+),\s+(?<year>\d+)\s+(?<hour>\d+):(?<minute>\d+)\s+(?<timezone>\w+)$/; const str = 'Tue May 20, 2019 16:30 GMT'; const { month, day, year, hour, minute, timezone } = str.match(reg).groups; console.log(month, day, year, hour, minute, timezone); // 'May', '20', '2019', '16', '30', 'GMT'
可以看到,上面的代码使用了ES9中的新特性——“命名捕获组”,通过在括号内使用(?<名字>
语法为捕获组命名,这使得后续的解构语法变得非常简单和直观,易于理解和维护。同时,我们还可以通过groups
属性,得到命名捕获组的值,同样的变量名和正则表达式中引用的名字保持一致,这使得代码语义更加清晰,易于阅读。
命名捕获组的语法
命名捕获组使用(?<name>
语法来定义。其中,name
是捕获组的名字,可以是任何合法的标识符。你可以像下面这样定义一个命名捕获组:
const reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; const str = '2019-06-10'; const match = str.match(reg); console.log(match.groups.year, match.groups.month, match.groups.day); // '2019', '06', '10'
当然,命名捕获组还支持使用数字命名(如(?<1>\d{4})
)和混合命名(如(?<1st_year>\d{4})
)。
命名捕获组和未命名捕获组的区别
使用命名捕获组的主要优势是,我们可以使用捕获组的名字直接引用一个组,而不是使用默认的索引。
如果你在正则表达式中使用了未命名的捕获组,那么它们可以通过索引在后续代码中使用。例如,在下面的例子中,我们使用了未命名的(或者说默认的)捕获组,然后使用了一个索引来直接引用这个组:
const reg = /(\d{4})-(\d{2})-(\d{2})/; const str = '2019-06-10'; const [_, year, month, day] = str.match(reg); console.log(year, month, day); // '2019', '06', '10'
不过,在后续代码中使用索引将导致代码不够明确,并且容易出错。而通过命名捕获组,我们可以大幅提高代码的可读性和可维护性,甚至可以在复杂的正则表达式中方便地引用不同的捕获组。
示例代码
让我们看一下几个实用的使用命名捕获组的示例代码:
抽取链接中的主机名
const reg = /^(?<protocol>https?:\/\/)?(?<host>[^\/\:]+)(?::\d+)?(\/.*)?$/i; const url = 'https://www.example.com/path/to/file.html'; const { host } = url.match(reg).groups; console.log(host); // 'www.example.com';
在上面的例子中,我们使用了命名捕获组(?<host>
匹配链接中的主机名。这个正则表达式还支持捕获链接中的协议名、主机端口和路径,但由于我们只需要主机名,所以我们只引用了捕获组host
。
替换字符串中的相邻字母
const reg = /(?<a>[a-z])(?<b>[a-z])/gi; const str = 'hello world'; const newStr = str.replace(reg, '$2$1'); console.log(newStr); // 'ehll oowlrd'
在上面的例子中,我们使用了命名捕获组(?<a>
和(?<b>
同时捕获两个相邻的字母,然后使用$2$1
实现了字母交换的效果。
抽取 log 数据
-- -------------------- ---- ------- ----- --- - ------------------------------------------------------------------------------------------------------------------------------------------- ----- --- - ----------- -------- ------ ------ -------- ----- ----- - --------------- -------------------------- -- - ----- ------- -- ------ ----- -- ---- ----- -- ----- ----- -- ------- ----- -- ------- ----- -- ------ ------- -- -------- ------- ------- -
在上面的例子中,我们使用了一条复杂的正则表达式,捕获了日志中的时间、级别和信息。使用命名捕获组(?<year>
等,可以方便地在后续代码中引用捕获到的数据。这使得代码的可读性和可维护性均得到了提升。
总结
使用命名捕获组是 ECMAScript 2018 带来的一个非常实用的新特性。它可以大幅提高代码的可读性和可维护性,并且方便后续代码中直接引用捕获到的数据,从而避免使用索引带来的风险和不方便。
在使用正则表达式时,我们应该尽可能使用命名捕获组,并结合具体的场景,使用其提供的更多高级特性来优化自己的代码。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/649e457248841e9894acdcae