原文连接: https://www.cnblogs.com/yalong/p/14133482.html
分组概念的由来:
对于要重复单个字符,非常简单,直接在字符后加上限定符即可,例如 a+ 表示匹配1个或一个以上的a,a?表示匹配0个或1个a, 这些限定符如下所示:
X ? |
X ,一次或一次也没有 |
X * |
X ,零次或多次 |
X + |
X ,一次或多次 |
X { n } |
X ,恰好 n 次 |
X { n ,} |
X ,至少 n 次 |
X { n , m } |
X ,至少 n 次,但是不超过 m 次
|
但是我们如果要对多个字符进行重复怎么办呢 此时我们就要用到分组,我们可以使用小括号"()"来指定要重复的子表达式,然后对这个子表达式进行重复,例如:(abc)? 表示0个或1个abc 这里一 个括号的表达式就表示一个分组 。
分组可以分为两种形式,捕获组和非捕获组。
捕获分组
捕获性分组工作模式()会把每个分组里匹配的值保存起来, 保存在内存中。
比如利用捕获性分组把 2010/11/12 转成 2010-11-12
方法一:通过exec函数
let str = '2010/11/12 let pattern = /(\d+)\/(\d+)\/(\d+)/ let arr = pattern.exec(str) console.log(arr); // ['2010/11/12', '2010', '11', '12'] console.log(arr[0]); //'2010/11/12' 匹配到的字符串 console.log(arr[1]); //'2010' 第一个分组(\d+)的值 console.log(arr[2]); //'11' console.log(arr[3]); //'12' //这时候两个分组的值都得到了,接下来用字符串拼接法实现互换 let result = `${arr[1]}-${arr[2]}-${arr[3]}` console.log(result) //2010-11-12
方法二:通过属性$1-9
let str = '2010/11/12' let pattern = /(\d+)\/(\d+)\/(\d+)/ pattern.test(str); //这个地方必须运行正则匹配一次,方式不限,可以是test()、exec()、以及String的正则方式 console.log(RegExp.$1) //'2010' 第一个分组([a-z]+)的值 console.log(RegExp.$2) //'11' console.log(RegExp.$3) //'12' let result = `${RegExp.$1}-${RegExp.$2}-${RegExp.$3}` console.log(result) //2010-11-12
方法三:通过String的replace()
let str = '2010/11/12' let pattern = /(\d+)\/(\d+)\/(\d+)/ let result = str.replace(pattern,"$1-$2-$3"); //这里的$1、$2与方法二里的RegExp.$1、RegExp.$2作用是相同的。 console.log(result) //2010-11-12
非捕获性分组:(?:)
非捕获性分组工作模式下分组(?:)会作为匹配校验,并出现在匹配结果字符里面,但不作为子匹配返回
非捕获组不会捕获文本,也不会将它匹配到的内容单独分组来放到内存中。所以,使用非捕获组较使用捕获组更节省内存
比如利用非捕获性分组获取字符串000aaa111,而且只返回一个值为aaa111的数组:
//先看用捕获性分组匹配会返回什么 let str1 = '000aaa111'; let pattern = /([a-z]+)(\d+)/; //捕获性分组匹配 let arr = pattern.exec(str1); console.log(arr) //['aaa111','aaa','111'] 结果子串也获取到了,这并不是我们想要的结果 //非捕获性分组 let str2 = '000aaa111'; let pattern2 = /(?:[a-z]+)(?:\d+)/; //非捕获性分组匹配 let arr2 = pattern2.exec(str2); console.log(arr2) //['aaa111'] 结果正确
断言分为先行断言(前瞻),后发断言(后顾)
前瞻 = 先行断言
(?=) 正向前瞻 = 正向零宽先行断言
(?!) 反向前瞻 = 负向前瞻 = 负向零宽先行断言
后顾 = 后发断言
(?<=) 正向后顾 = 正向零宽后发断言
(?<!) 反向后顾 = 负向后顾 = 负向零宽后发断言
详细解释如下表所示:
(?=X ) |
零宽度正先行断言。仅当子表达式 X 在 此位置的右侧匹配时才继续匹配。例如,/w+(?=/d) 与后跟数字的单词匹配,而不与该数字匹配。此构造不会回溯。 |
(?!X) |
零宽度负先行断言。仅当子表达式 X 不在 此位置的右侧匹配时才继续匹配。例如,例如,/w+(?!/d) 与后不跟数字的单词匹配,而不与该数字匹配 。 |
(?<=X) |
零宽度正后发断言。仅当子表达式 X 在 此位置的左侧匹配时才继续匹配。例如,(?<=19)99 与跟在 19 后面的 99 的实例匹配。此构造不会回溯。 |
(?<!X) |
零宽度负后发断言。仅当子表达式 X 不在此位置的左侧匹配时才继续匹配。例如,(?<!19)99 与不跟在 19 后面的 99 的实例匹配 |
简单来说,前瞻就是 看后面等于(?=), 后面不等于 (?!), 后顾就是 前面等于 (?<=), 后面不等于 (?<!)
比如 (?<!AA)eat(?=milk) 表示, eat 前面不能是AA, 后面必须是 milk
比如 (?<=AA)eat(?!milk) 表示 eat 前面必须是AA, 后面不能是milk
示例1 匹配字符串:
let str1 = "VVeatmilk" let str2 = "AAeatfood" let patt1 = new RegExp("(?<!AA)eat(?=milk)"); let patt2 = new RegExp("(?<=AA)eat(?!milk)"); let result1 = patt1.test(str1); let result2 = patt2.test(str2); console.log(result1) // true console.log(result2) // true
示例2 匹配div标签:
let str = "<div>我是div</div>" let patt = str.match('(?<=<div>).*(?=</div>)') console.log(patt) // 我是div
匹配div标签 也可用 下面这种方式实现
let str = "<div>我是div</div>" let patt = str.match('<div>(.*?)</div>') console.log(patt) // 我是div