zoukankan      html  css  js  c++  java
  • JS 正则中的命名捕获分组

    假设你在一段陌生的代码中看到这样一个函数: 

    function toLocalDate(date) {
      return date.replace(/(d{2})-(d{2})-(d{4})/, "$2-$1-$3")
    }

    单看这个函数你能知道它是想把“日-月-年”替换成“月-日-年”,还是反过来?匿名捕获分组没法做到这一点,那就该命名捕获分组上场了:

    function toLocalDate(date){
      return date.replace(/(?<month>d{2})-(?<day>d{2})-(?<year>d{4})/, "$<day>-$<month>-$<year>")
    }

    俗话说的好,“一个好的变量名赛过一行注释”,命名捕获分组很大的一个作用就是它能起到注释的作用。

    另外,命名捕获分组还有一个好处,那就是假如你在修改一个正则时,在已有分组的左边引入了新的分组,那么你还得记得更新已有的反向引用的数字。比如将 (foo)1 改成了 (bar)(foo)1,那你得把原来的 1 改成 2,replace() 方法的第二个参数里的 $1 也同样得改,用命名分组不会有这个问题。

    语法

    命名捕获分组自身的语法是 (?<name>...),比普通的分组多了一个 ?<name> 字样,其中 name 的起法就和你平时起变量名一样即可(不过在这里关键字也可用)。

    反向引用一个命名分组的语法是 k<name>,注意命名分组同样可以通过数字索引来反向引用,比如:

    /(?<foo>a)k<foo>1/.test("aaa") // true

    在 replace() 方法的替换字符串中反向引用是用 $<name>:

    "abc".replace(/(?<foo>a)/, "$<foo>-") // "a-bc",同样 $1 仍然可用

    总结一下就是,和命名分组相关的有三种语法,分别是 ?<name>、k<name>、$<name>,相同点是都用尖括号包裹着分组名。

    在 API 中的使用

    在 exec() 和 match() 中的使用:

    const groups = "04-25-2017".match(/(?<month>d{2})-(?<day>d{2})-(?<year>d{4})/).groups // {month: "04", day: "25", year: "2017"}
    
    const {day, month, year} = groups

    exec() 和 match() 方法返回的匹配结果数组上多了一个 groups 属性,里面存放着每个命名分组的名称以及它们匹配到的值,利用 ES6 的解构语法,可以方便的提取出想要的字段。注意这个 groups 属性只有在当前正则里至少存在一个命名分组的前提下才会存在,比如:

    /(d{2})-(d{2})-(d{4})/.exec("04-25-2017").groups // undefined,因为没有命名分组

    在 replace(/.../, replacement) 中的使用:

    replacement 是字符串的情况上面已经举过例子了,这里主要讲它是函数的情况:

    "04-25-2017".replace(/(?<month>d{2})-(?<day>d{2})-(?<year>d{4})/, (...args) => {
      const groups = args.slice(-1)[0]
      const {day, month, year} = groups
      return `${day}-${month}-${year}`
    }) // "25-04-2017"

    也就是说,在实参列表的最末尾,多传了一个 groups 对象。同样,如果正则里没有命名分组,这个参数不会存在。

    异常情况

    分组名不能有重复项:

    /(?<foo>a)(?<foo>b)/ // SyntaxError: Duplicate capture group name

    反向引用一个不存在的分组名:

    /k<foo>/u // SyntaxError: Invalid named capture referenced
    
    /k<foo>/.test("k<foo>") // true, 非 Unicode 下为了向后兼容,k 前面的  会被丢弃

    在 reaplce() 方法的替换字符串中引用一个不存在的分组:

    "abc".replace(/(?<foo>.*)/, "$<bar>") // SyntaxError: Invalid replacement string
    
    "abc".replace(/(.*)/, "$<bar>") // "$<bar>",不包含命名分组时会向后兼容

    总结

    V8 目前已经完全实现了命名捕获分组的提案 https://tc39.github.io/proposal-regexp-named-groups/

    命名分组虽然带来了一些好处,但我个人觉得,正则越长越难读懂,尤其增加的长度是一堆小括号和尖括号。在可读性上,命名分组也许会起到反作用,尤其对正则苦手来说。 

  • 相关阅读:
    定位--position属性
    浮动
    超链接伪类
    对齐方式
    字体样式
    标准文档流
    CSS
    表单
    框架
    内部类
  • 原文地址:https://www.cnblogs.com/ziyunfei/p/6761413.html
Copyright © 2011-2022 走看看