IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    javascript正则表达式的进阶介绍

    风如也发表于 2023-07-25 12:22:00
    love 0

    作者:yrainly

    公众号:前端开心果

    正则表达式进阶内容

    👉正则表达式基础介绍内容请戳这里哦

    先补充下之前介绍过的replace()方法的使用。

    replace()方法详解

    replace()方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。

    replace 方法有三种形态:

    • String.prototype.replace(str, replaceStr)
    • String.prototype.replace(reg, replaceStr)
    • String.prototype.replace(reg, function)

    使用示例1:

    const str = 'Hello Kaixinguo'
    // 正则表达式
    str.replace(/ello/, 'ey') // hey Kaixinguo
    str.replace(/elll/, 'ey') // Hello Kaixinguo
    // 字符串:字符串参数会转换为正则表达式
    str.replace('ello', 'ey') // hey Kaixinguo
    str.replace('elll', 'ey') // Hello Kaixinguo
    ​
    str.replace(/o/g, function (...args) {
      console.log(args)
    })
    // ['o', 4, 'Hello Kaixinguo']
    // ['o', 14, 'Hello Kaixinguo']

    replace 接受函数参数时,有四个参数:

    • 匹配字符串
    • 正则表达式分组内容,没有分组则没有该参数
    • 匹配项在字符串中的 index
    • 原字符串

    上面的使用示例1,replace接收函数参数时没有分组,所以打印出来的只有3个参数。

    下面示例演示正则表达式使用了分组的情况。

    使用示例2:

    const str = 'Hello Kaixinguo,'
    ​
    str.replace(/(\w{2})/g, function (...args) {
      console.log(args)
    })
    // 有几个分组就有几个group
    /**
     * [match, group1, index, origin]
     * ['He', 'He', 0, 'Hello Kaixinguo,']
      ['ll', 'll', 2, 'Hello Kaixinguo,']
      ['Ka', 'Ka', 6, 'Hello Kaixinguo,']
      ['ix', 'ix', 8, 'Hello Kaixinguo,']
      ['gu', 'gu', 12, 'Hello Kaixinguo,']
     * **/

    后行断言

    支持先行断言(lookahead),先行否定断言(negative lookahead),后行断言(lookbehind)和后行否定断言(negative lookbehind)。ES2018 引入后行断言。

    表达式描述
    ?=n匹配任何其后紧接指定字符串 n 的字符串
    ?!n匹配任何其后没有紧接指定字符串 n 的字符串

    先行断言

    x只有在 y前面才匹配,必须写成/x(?=y)/。比如,只匹配百分号之前的数字,要写成/\d+(?=%)/。

    先行否定断言

    x只有不在 y前面才匹配,必须写成/x(?!y)/。比如,只匹配不在百分号之前的数字,要写成/\d+(?!%)/。

    使用示例:

    // 先行断言
    let behead = /\d+(?=%)/.exec('关注我的人中奖率甚至都达到了100%')
    console.log(behead)
    // ['100', index: 14, input: '关注我的人中奖率甚至都达到了100%', groups: undefined]
    ​
    // 先行否定断言
    let notBehead = /\d+(?!%)/.exec('有14个人关注了我,他们的中奖率能到100%')
    console.log(notBehead)
    // ['14', index: 1, input: '有14个人关注了我,他们的中奖率能到100%', groups: undefined]

    上面两个字符串,如果互换正则表达式,就不会得到相同的结果。另外,还可以看到,“先行断言”括号之中的部分((?=%)),是不计入返回结果的。

    后行断言

    正好与“先行断言”相反,x只有在 y 后面才匹配,必须写成 /(?<=y)x/。比如,只匹配美元符号后面的数字,要写成/(?<=$)\d+/。

    后行否定断言

    则与“先行否定断言”相反,x 只有不在y后面才匹配,必须写成 /(?<!y)x/。比如,只匹配不在美元符号后面的数字,要写成 /(?<!$)\d+/。

    使用示例:

    // 后行断言
    let lookbehead = /(?<=$)\d+/.exec('关注我的人都会中$100万')
    console.log(lookbehead)
     // ['100', index: 9, input: '关注我的人都会中$100万', groups: undefined]
    ​
    // 后行否定断言
    let negativeLookbehead = /(?!=$)\d+/.exec('有14个人关注了我,他们都中了$100万') 
    console.log(negativeLookbehead)
    // ['14', index: 1, input: '有14个人关注了我,他们都中了$100万', groups: undefined]

    具名组匹配

    正则表达式使用圆括号进行组匹配

    const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/

    上面代码中,正则表达式里面有三组圆括号。使用 exec 方法,就可以将这三组匹配结果提取出来。

    const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/
    const matchObj = RE_DATE.exec('1997-11-26')
    console.log(matchObj)
    // [ '1997-11-26', '1997', '11', '26', index: 0, input: '1997-11-26', groups: undefined ]
    const year = matchObj[1] // 1997
    const month = matchObj[2] // 11
    const day = matchObj[3] // 26

    组匹配的一个问题是,每一组的匹配含义不容易看出来,而且只能用数字序号(比如 matchObj[1])引用,要是组的顺序变了,引用的时候就必须修改序号。

    ES2018 引入了具名组匹配,允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用。

    const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
    ​
    const matchObj = RE_DATE.exec('1997-11-26')
    console.log(matchObj)
    // [ '1997-11-26', '1997', '11', '26', index: 0, input: '1997-11-26', groups: [Object: null prototype] { year: '1997', month: '11', day: '26' } ]
    ​
    const year = matchObj.groups.year // 1997
    const month = matchObj.groups.month // 11
    const day = matchObj.groups.day // 26

    上面代码中,“具名组匹配”在圆括号内部,模式的头部添加“问号+尖括号+组名”(?<year>),然后就可以在 exec 方法返回结果的 groups 属性上引用该组名。同时,数字序号 (matchObj[1]) 依然有效。

    具名组匹配等于为每一组匹配加上了ID,便于描述匹配的目的。如果组的顺序变了,也不用改变匹配后的处理代码。

    如果具名组没有匹配,那么对应的 groups 对象属性会是 undefined。

    const reg = /^(?<as>a+)?$/
    const matchObj = reg.exec('')
    console.log(matchObj)
    console.log(matchObj.groups.as) // undefined
    console.log('as' in matchObj.groups) // true
    /**
     * [
      '',
      undefined,
      index: 0,
      input: '',
      groups: [Object: null prototype] { as: undefined }
    ]
     */

    上面代码中,具名组 as 没有找到匹配,那么 matchObj.groups.as 属性值就是 undefined,并且 as 这个键名在 groups 是始终存在的。

    解构赋值和替换

    有了具名组匹配以后,可以使用解构赋值直接从匹配结果上为变量赋值。

    let { groups: { one, two } } = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar')
    console.log(one, two) // foo bar

    字符串替换时,使用 $<组名>引用具名组。

    let reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u
    console.log('2006-06-07'.replace(reg, '$<day>/$<month>/$<year>')) // 07/06/2006

    上面代码中,replace 方法的第二个参数是一个字符串,而不是正则表达式。

    replace 方法的第二个参数也可以是函数,该函数的参数序列如下。

    let reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u
    let str = '2015-01-02'.replace(reg, (
       matched, // 整个匹配结果 2015-01-02
       capture1, // 第一个组匹配 2015
       capture2, // 第二个组匹配 01
       capture3, // 第三个组匹配 02
       position, // 匹配开始的位置 0
       S, // 原字符串 2015-01-02
       groups // 具名组构成的一个对象 {year, month, day}
     ) => {
      let { day, month, year } = groups
      return `${day}/${month}/${year}`
    })
    console.log(str) // 02/01/2015

    具名组匹配在原来的基础上,新增了最后一个函数参数:具名组构成的一个对象。函数内部可以直接对这个对象进行解构赋值。

    let reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u
    ​
    let str1 = '2015-01-02'.replace(reg, (...args) => {
      console.log(args)
      let { day, month, year } = args[6]
      return `${day}/${month}/${year}`
    })
    console.log(str1)
    /**
     * [
      '2015-01-02',
      '2015',
      '01',
      '02',
      0,
      '2015-01-02',
      [Object: null prototype] { year: '2015', month: '01', day: '02' }
    ]
     */

    引用

    如果要在正则表达式内部引用某个“具名组匹配”,可以使用 \k<组名>的写法。

    const reg = /^(?<word>[a-z]+)!\k<word>$/
    reg.test('abc!abc') // true
    reg.test('abc!ab') // false

    数字引用(\1)依然有效。

    const reg = /^(?<word>[a-z]+)!\1$/
    reg.test('abc!abc') // true
    reg.test('abc!ab') // false

    这两种引用方法还可以同时使用。

    const reg = /^(?<word>[a-z]+)!\k<word>!\1$/
    reg.test('abc!abc!abc') // true
    reg.test('abc!ab!ab') // false

    最后,分享几个可以在线测试正则表达式的地址:

    • https://jex.im/regulex/
    • https://regex101.com/
    • http://tool.oschina.net/regex/

    结语

    ❤️ 🧡 💛大家喜欢我写的文章的话,欢迎大家点点关注、点赞、收藏和转载!!

    欢迎关注公众号前端开心果 🔥我会持续更新前端相关的内容文章哦。



沪ICP备19023445号-2号
友情链接