js正则高级用法: 分组和断言

原文连接: 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

 

posted @ 2020-12-14 17:13  进军的蜗牛  阅读(1326)  评论(0编辑  收藏  举报