用大白话轻松搞定正则(下)

4,083 阅读7分钟

前言

本文是 轻松搞定js正则上 的一篇续文,建议大家在看本篇文章之前,先看 轻松搞定js正则上,(正则基础好和大佬可以忽略),文章中可能有不足的地方,也希望大家可以在评论区提出来,大家一起学习(本人菜的抠脚),这篇文章也是断断续续的接近半个月才写完(主要也是因为忙,后来又遇见了双节的技术正文,于是就又先写了一起学重绘和回流 这篇征文),最后大家一起来聊聊这篇水文吧!如果这篇文章对您有帮助,可以关注我的公众号,咱们一起学前端😄

选择匹配

选择匹配类似 JavaScript 的逻辑运算中的 || 表示 的意思,在js正则中 ,有一个匹配模式叫做 选择匹配

使用的是 | ,它的语法就是 x|y 先上一个简单的案例:

let str='abcd';
let reg=/a|ab/;
let res=str.match(reg);
console.log(res)//["a", index: 0, input: "abcd", groups: undefined]

选择匹配 次序是从左到右,直到发现了匹配项,就忽略右边的匹配项(即使它产生了更好的匹配)。

上面的匹配即便ab更合适但还是只会匹配a

再上个🌰,我们想使用选择匹配来匹配一段字符,只能是ab 或者 cd。上代码:

let reg=/^ab|cd$/;
console.log(reg.test('ab'));//true   ab或者是cd中的一个,所以为true
console.log(reg.test('cd'));//true   ab或者是cd中的一个,所以为true
console.log(reg.test('abc'));//true  以ab开头,所以为 true
console.log(reg.test('abd'));//true  以 a开头,中间b或者c,以d结尾,所以为true
console.log(reg.test('acd'));//true  以 a开头,中间b或者c,以d结尾,所以为true
console.log(reg.test('abcd'));//true 以ab开头 或者 以cd结尾 ,所以为true
console.log(reg.test('bcd'));//true  以cd 结尾,所以为true
console.log(reg.test('bacd'));//true 以cd 结尾,所以为true

上面的输出的结果是不是很意外,我们想要的结果就是 ab 或者 cd 会输出 true ,其他的则是false,导致上面出现的原因,就是优先级的问题。

直接使用 x|y 也就是选择匹配,如果进行选择匹配的字符在 两个以及两个以上 , 会存在很乱的优先级的问题,正则里面内部到底是怎么样的运算规则会出现这样的问题,笔者目前也还没有搞清楚(希望能够和各位大佬在评论区讨论学习),所以在使用选择匹配的时候一般不单独的使用,通常在使用选择匹配的时候,都会使用小括号 进行一个分组的,小括号可以改变优先级,具体怎么怎么改变,咱夹着往下看哈

分组匹配

上面咱们也聊了,可以使用分组用来更改 正则匹配的时候的 优先级,在大家上小学的时候就知道 ,在进行算数运算的时候 可以使用 ()来提高计算的优先级,同样在正则中,我们也是可以使用 () 来进行分组匹配来提高匹配的优先级

这是一个 小数的数学运算题
1+2+(2*3)
=1+2+6
=3+6
=9

接着咱来看看使用分组匹配来改变选择匹配的优先级问题

let reg=/^(ab|cd)$/;
console.log(reg.test('ab'));//true
console.log(reg.test('cd'));//true
console.log(reg.test('abc'));//false
console.log(reg.test('abd'));//false
console.log(reg.test('abcd'));//false
console.log(reg.test('bcd'));//false
console.log(reg.test('bacd'));//false

在代码中,使用 () 进行分组,改变优先级,先匹配 ab|cd ,然后才是 ^$

分组的捕获和引用

被正则表达式捕获(也就是匹配)到的字符串会被暂存起来,这些被捕获到的字符串,会从左到右 开始 第一个分组捕获到的字符就会 被标记为 1 开始进行编号,后面的一次类推,

let str = '2020-09-29';
let reg = /(\d{4})-(\d{2})-(\d{2})/;
let res=reg.test(str);  
console.log(res);//true
console.log("第一个分组匹配到的字符",RegExp.$1);
console.log("第二个分组匹配到的字符",RegExp.$2);
console.log("第三个分组匹配到的字符",RegExp.$3);

🌰使用正则中的分组来做一个创建的日期格式替换的Demo

聊到替换,大家肯定会联想到咱们在上一篇文章中所聊到的 replace() 方法,下面是使用它结合 分组,实现日期替换的两种方式:👇👇👇

方法一:

let str = '现在北京时间2020:09:29';
let reg = /(\d{4}):(\d{2}):(\d{2})/;
str = str.replace(reg,'$1-$2-$3') 
console.log(str)//现在北京时间2020-09-29

方法二:

let str = '现在北京时间2020:09:29';
let reg=/:/g;
str = str.replace(reg,'-');
console.log(str)//现在北京时间2020-09-29

着两种方式看似都可以实现 上面的功能,但是我会选择第一种方案进行使用 ,你知道答案是什么嘛??

原因很简单,因为第一种方法 的正则中/(\d{4}):(\d{2}):(\d{2})/ 匹配到的是我们常见的事件格式,即使是在一个很长的字符串中,能够被它捕获到的也只有这样的事件格式了,而第二种方法中的 正则/:/g 可以捕获到的则是字符中中所有的: ,而且还都会被替换成 -,而在一个很长的字符中中,用到 字符: 的地方有很多,都会被替换成-

命名捕获分组

命名捕获分组,用大白话来说就是,对捕获分组进行一个命名,它的一个语法就是 (?<name>...) ,也就是比咱们上面聊的分组中的普通分组多了 ?<name> ,其实这里面的 name 就和咱们平常取的变量名一样。

let str="现在是北京时间2020-09-30";
let reg=/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
//exec() 和 match() 方法返回的匹配结果数组上多了一个 groups 属性,里面存放着每个命名分组的名称以及它们匹配到的值,
let res=str.match(reg).groups;
console.log(res);

可以通过 解构赋值 获取到匹配到是 年月日,需要将日期换成哪种格式,再做一个字符串的拼接就可以啦😄

分组匹配不捕获

(?:) 只匹配不捕获;

有的时候只是为了分组并不需要捕获的情况下就可以使用非捕获型分组,例如:

let str = '2020-09-29';
let reg = /(?:\d{4})-(\d{2})-(\d{2})/
console.log(reg.test(str));
console.log("RegExp.$1:",RegExp.$1);
console.log("RegExp.$2:",RegExp.$2);

在字符中2020-09-29 中 使用正则 /(?:\d{4})-(\d{2})-(\d{2})/ 进行匹配,reg.test(str) 打印的结果为 true ,说明了 字符中是满足正则的匹配条件的,RegExp.$1 打印的结果是 09,是因为第一个分组 (?:\d{4}) 只进行了匹配,没有进行捕获。

正向预查(前瞻)

正向预查(也叫做 前瞻),其中包含了 正向肯定预查正向否定预查 两种方式。这里大家先记住即可,后面会详细聊的

正向肯定预查

正向肯定预查 它的一个语法就是 (?=pattern) 其中这里面的字符 pattern 表示的就是 要获取到这个字符,但是不匹配出来 (仅仅作为匹配的时候的条件限制,不会把匹配的结果精输出),是不是感觉有点绕,不好理解,咱直接上代码聊

let reg=/\d+(?=元)/g;
console.log(reg.test("399元"));//true
console.log("399元".match(reg))//399

在代码中 /\d+(?=元)/g 匹配的是 一次或多次的数字,后面 肯定 跟着一个,格式为:1234元(即数字后面必须是元),但是正则捕获的只是前面的数字,预查的 是不捕获的

let reg=/\d+(?=元)/g;
console.log(reg.test("399元"));//true
console.log("399元".match(reg))//399
console.log(reg.test("499磅"));//false  
console.log("499磅".match(reg))//null

在代码中 /\d+(?=元)/g 正向肯定(括号中的?后面是=)预查的是,所以 字符串 399元 符合了正则表达式匹配的规则,即 reg.test("399元") 的结果是 true , "399元".match(reg) 的结果是399 ;而在代码 reg.test("499磅") 中,数字后面的结尾是 字,不符合 /\d+(?=元)/g 预查的 ,所以 reg.test("499磅") 的结果为 false, "499磅".match(reg) 的结果为 null ,即没有匹配的结果。

正向否定预查

正向否定预查,刚好是和正向肯定预查 是对立,正向肯定预查是 (?=pattern) 那正向否定预查就是 (?!pattern),前面聊到了 /\d+(?=元)/g 匹配的是 数字后面肯定是 ,那 /\d+(?!元)/g 匹配的是什么哪,别着急,往下看哈;

let reg=/\d+(?!元)/g;
console.log("399元".match(reg))//39
console.log("3990元".match(reg))//399
console.log("499磅".match(reg))//499
console.log("599¥".match(reg))//599

(?!元) 是正向否定预查 这个字符 (不是 元 这个字符),那 /\d+(?!元)/g 可以捕获到的就是,数字后面不是元的字符 ;所以是不是觉得上面的代码中的第4行和第5行的代码的结果在你的预料之中,但是第二行和第三行的结果是不是有点不太理解,为什么第二行的结果是 39,而不是399,因为如果是 399的话,后面的数字就是,这里是否定预查,数字后面的字符,不是,所以能够捕获到的就是 39,它后面的字符是 9 ,不是

有了正向预查的基础,接下来再聊聊反向预查⬇⬇⬇⬇⬇⬇⬇

反向预查(后顾)

在上面,咱们聊到了正向预查也叫做前瞻,大家可以结合上面的案例代码,仔细的揣摩一下,首先不管是正向预查还是反向预查(后顾),它们都是在匹配的时候作为一个条件的限制,不会把匹配的结果进行输出。在正向肯定预查的案例代码中,/\d+(?=元)/g 是以 这个汉字 作为分界点, 这个汉字的 前面 是数字,也就是 数字的后面肯定是 ,同样正向否定预查也是这个道理。到这里是不是对正向预查又理解的更深一步了呢!!(这段文字放在这里主要是能够让大家去对比着去理解正向预查和反向预查)

反向预查从它的名字上也看的出来,它和正向预查是相反的,反向预查也叫做后顾,同样它也有反向肯定预查反向否定预查两种方式;

反向肯定预查

反向肯定预查 它的一个语法是 (?<=pattern) 其中这里面的字符 pattern 表示的就是 要获取到这个字符,但是不匹配出来 (仅仅作为匹配的时候的条件限制,不会把匹配的结果精输出)

let reg=/(?<=¥)\d+/g;
console.log("¥399".match(reg))//399
console.log("¥3990".match(reg))//3990
console.log("$499".match(reg))//null

也就是说,数字前面 一定 这个字符,但是字符 是只获取,不会把匹配的结果进行输出,所以第二行的代码输出的是399,第三行的代码输出的是3990,第四行中的字符$499 数字前面不是 ,即不满足匹配条件(数字前面的字符必须是的数字)所以没有匹配到结果。

反向否定预查

反向否定预查 它的一个语法是 (?<!pattern) 其中这里面的字符 pattern 表示的就是 要获取到这个字符,但是不匹配出来 (仅仅作为匹配的时候的条件限制,不会把匹配的结果精输出)

let reg=/(?<!¥)\d+/g;
console.log("¥399".match(reg))//99
console.log("¥3990".match(reg))//990
console.log("$499".match(reg))//499

正则/(?<!¥)\d+/g 获取数字的前面,不是 的数字,第二行中的代码 "¥399".match(reg) 获取的是数字的前面是不 的数字,(有点绕,仔细揣摩一下),99的前面是 数字3 ,不是 符合匹配的条件,所以输出的结果是99, 第三行也是同样的道理,第四行的代码中"$499".match(reg) 中,数字499前面的字符是$ ,不是 ,所以满足匹配条件的是499

贪婪匹配与非贪婪匹配

难点:贪婪模式/惰性模式(非贪婪)

贪婪模式——在匹配成功的前提下,尽可能多的去匹配(它比较贪心,想要的更多😄)

惰性模式——在匹配成功的前提下,尽可能少的去匹配(它比较懒惰,尽可能的少匹配罒ω罒)

// 贪婪匹配
let greedy = 'aaaaaa'.match(/a+/g);
console.log('greedy',greedy );
// 非贪婪匹配
let lazy = 'aaaaaa'.match(/a+?/g);
console.log('lazy',lazy);

在上面的代码中,'aaaaaa'.match(/a+/g) 就是一个贪婪的,其中正则/a+/g 是在全局中,匹配字符a 一次或多次 ,在匹配成功的前提下,尽可能的去多匹配,一直匹配到无法匹配为止,所以匹配到的结果就是["aaaaaa"] , 然而 'aaaaaa'.match(/a+?/g) 就是 一个非贪婪的(也就是 懒惰的),虽然只是多了一个 ?,但是匹配的结果却有很大的不同,? 表示的是 零次或一次 , 在贪婪模式的后面加上? 就变成了懒惰,也就是会尽可能去少匹配,即在匹配的过程中,一旦匹配到结果就会终止匹配(因为它是懒惰的罒ω罒)。

历史文章

  1. 一起学重绘和回流 | 掘金技术征文-双节特别篇
  2. 用大白话轻松搞定正则(上)
  3. js垃圾回收机制原理给你聊的明明白白
  4. ES11来了,不进来看看嘛
  5. 秒懂js作用域与作用域链
  6. 你需要的git命令大全来了