1. 正则表达式的创建
正则表达式有两种创建方法
- 通过/abc/,仅支持静态正则
- 通过new RegExp('abc'),支持动态正则
推荐使用第二种,采用new RegExp()模式,优点在于动态正则,本质是因为字符串可拼接
2. 正则表达式的方法
在了解具体的正则配置之前,有必要先掌握清楚正则每个方法的作用,这样才能够帮助更好的理解正则 可用于正则的一共有两种数据类型
- RegExp对象方法
- String对象方法
1. RegExp - test方法
概念:reg的test方法,用来验证正则是否匹配,返回布尔值
const reg = /A/;
reg.test('BA'); // true
这是最简单的用法,和search的区别是一个返回布尔,一个返回下标 注意:test可以在多次调用,前提是设置模式为/g
const reg = /A/g;
const str = 'A B A C';
reg.test(str); // true;
reg.lastIndex // 1
reg.test(str); // true;
reg.lastIndex // 5
reg.test(str); // false;
reg.lastIndex // 0
- reg的test方法每次匹配的位置是不一样的
- lastIndex表示后续匹配将从lastIndex之后开始
- reg第三次匹配返回false,并将lastIndex重置了
2. RegExp - exec方法
概念:reg的exec方法,每调用一次会匹配一个字符串的命中项,方便进行流程管控
const reg = /AB/g;
const str = 'DCAB ABCD CABD'
reg.exec(str) // ["AB", index: 2, input: "DCAB ABCD CABD", groups: undefined]
reg.lastIndex; // 4
reg.exec(str) // ["AB", index: 5, input: "DCAB ABCD CABD", groups: undefined]
reg.lastIndex; // 7
reg.exec(str) // ["AB", index: 11, input: "DCAB ABCD CABD", groups: undefined]
reg.lastIndex; // 13
reg.exec(str) // null
reg.lastIndex; // 0
- reg设置为了AB,全局g匹配
- reg匹配str ='DCAB ABCD CABD';
- 返回结果,包含匹配值,下标等
- reg对象的lastIndex,更新为下一次匹配的开始位置4
- reg再次匹配str
- 再次返回结果
- reg对象的lastIndex,更新为下一次匹配的开始位置7
- reg再次匹配str
- 再次返回结果
- reg对象的lastIndex,更新为下一次匹配的开始位置13
- reg再次匹配str
- 匹配不到了,返回null
- reg对象的lastIndex重置为0
RegExp的方法一共就两个,都可以反复调用
- 常规用test就够
- 想精细化操作用exec
3. String - search方法
概念:str通过search验证是否匹配某一正则
const reg = /A/;
'AB'.search(reg); // 0;
'BA'.search(reg); // 1;
'C'.search(reg); // -1;
---
'AB AB'.search(reg); // 0;
'AB AB'.search(reg); // 0;
- 此时字符串是主体
- AB检查是否匹配A,得到下标0;
- BA检查是否匹配A,得到下标1;
- C检查是否匹配A,未找到,返回-1;
- AB AB检查是否匹配A,得到下标0;
- AB AB检查是否匹配A,依旧得到第一个检索到的下标0;
search不能进行迭代查询,不能判断有多少个匹配,使用场景受限:
- 仅需要知道是否匹配(test也满足)
- 且要获取第一个匹配的下标(这一点比test强,但test也能通过lastIndex计算得出index)
4. String - match方法
概念:str的match将匹配符合正则的元素
const reg = /A/;
'AB'.match(reg); // ["A", index: 0, input: "AB", groups: undefined]
'BA'.match(reg); // ["A", index: 1, input: "BA", groups: undefined]
---
const str = 'AB AB';
str.match(reg); // ["A", index: 0, input: "AB AB", groups: undefined]
str.match(reg); // ["A", index: 0, input: "AB AB", groups: undefined]
---
- reg设置匹配A
- AB进行匹配,匹配到A,返回结果的相关信息
- BA进行匹配,匹配到A,返回结果的相关信息
- 创建str包含重复的A
- str进行匹配,匹配到第一个A,返回结果相关信息
- str进行匹配,还是匹配到第一个A,返回结果相关信息
match方法,也不能进行迭代查询,仅能拿到匹配的第一个结果,返回值和exec相同 有一点需要注意,就是设置/g全局匹配时
const reg = /A/g;
'AB AB'.match(reg); // ["A", "A"]
- reg全局匹配A
- str进行match后,返回所有符合的结果集合
- 注意:此结果集不包含下标信息等内容,仅包含匹配结
- 只能用在快速得到有多少个符合的结果上
5. String - matchAll方法
前面刚提过,match的缺点,在获取多匹配结果时并不能方便 matchAll就是快速解决这些问题的,通过matchAll会返回迭代器对象,关于迭代器,这里先不展开
const reg = /A/g;
[...'AB AB'.matchAll(reg)];
// 输出如下:
// [
// ["A", index: 0, input: "AB AB", groups: undefined],
// ["A", index: 3, input: "AB AB", groups: undefined]
// ]
可以看到,通过matchAll就拿到了所有的信息 同时结合for of还能解决exec的书写麻烦问题,不再需要while+exec循环了
const reg = /A/g;
const matchs = 'AB AB'.matchAll(reg);
for(let match of matchs){
console.log(match);
}
// ["A", index: 0, input: "AB AB", groups: undefined]
// ["A", index: 3, input: "AB AB", groups: undefined]
注意,matchAll,必须在定义reg时,要明确标明模式为/g全局,否则将报错
6. String - replace方法
replace方法很常用,但还是需要查缺补漏
str.replace(regexp|substr, newSubStr|function)
replace方法接受两个参数
- 参数1:可以是正则,也可以是固定的字符串
- 参数2:可以是新字符,也可以是回调函数
'AB'.replace('A','C') // CB
'AB'.replace(/A/,'C') // CB
'AB'.replace('A',()=>'C') // CB
---
'ABA'.replace('A','C') // CBA
'ABA'.replace('A','C').replace('A','C') // CBC
- 支持纯静态字符串A->C
- 支持reg形式的正则替换
- 支持函数动态处理
- 每一个replace只能处理一个最先匹配到的结果
替换字符串的特殊操作
'AB'.replace(/A/,'C') // CB
'AB'.replace('A',()=>'C') // CB
上面例子,第二个参数是字符串/函数都行,很明显函数定制性更强,字符串的操作空间较少 为了解决字符串不方便快速操作的问题,第二个字符串对象还有不少快捷操作,都是以$为主
6.1 $美元符号
这个操作其实意义不大,了解就好,主要是将2个美元符号,替换成1个
'AB'.replace('A','$$') // $B
'AB'.replace('A','$$$') // $$B
'AB'.replace('A','$$$$') // $$B
- 匹配到A,替换为美元符号,发现是两个$$,会替换成一个$
- 匹配到A,替换为美元符号,发现是三个$$$,其中两个会替换成一个+$ = $$;
- 匹配到A,替换为美元符号,发现是四个$$$$,其中两个会替换成一个$,所以结论是$$;
上面👆这个了解就好,不用深入
6.2 $&最后匹配到的字符串
概念:$&代表匹配到的结果
'AB'.replace(/A/,'C') // CB
RegExp.lastMatch // A
RegExp['$&'] // A
'AB'.replace(/A/,'$&') // AB
- AB通过正则匹配A,替换成C,输出CB
- 注意:全局对象RegExp上lastMatch的值更新为了,匹配结果A
- 同时RegExp上的[$&]也变成了A
- 在参数字符串中使用$&,也是同样的效果
结论:不推荐,因为RegExp是全局对象,任意一个正则触发都会影响到RegExp对象,安全性极低
6.3 $`最后匹配结果的左侧内容
概念:同上面的概念相似,只是变为了匹配结果的左侧
'CBA'.replace(/A/,'D'); // CBD
RegExp.leftContext; // CB
RegExp['$`']; // CB
'CBA'.replace(/A/,'$`'); // CBCB
- CBA通过正则/A/匹配,替换内容为D,结果为CBD
- 注意:全局对象RegExp上leftContext的值更新为了,匹配结果A的左侧内容CB
- 同时RegExp上的[$`]也变成了CB
- 在参数字符串中使用$`,就输出了结果CBCB
6.4 $'最后匹配结果的右侧内容
概念:和上面的正好相反
'ABC'.replace(/A/,'D'); //DBC
RegExp.rightContext //BC
RegExp['$\''] //BC
'ABC'.replace(/A/,'$\''); //BCBC
- ABC通过正则/A/匹配,替换内容为D,结果为DBC
- 注意:全局对象RegExp上rightContext的值更新为了,匹配结果A的右侧内容BC
- 同时RegExp上的[$']也变成了BC
- 在参数字符串中使用$',就输出了结果BCBC
6.5 $n 括号结果匹配
概念:正则中括号包裹的内容,会被挂载到$n上 注意:下标由1开始
'AB'.replace(/(A)/g,'C'); // CB
RegExp.$1 // A
RegExp.$2 // ''
---
'ABC'.replace(/(A).(C)/g,'');
RegExp.$1 // A
RegExp.$2 // C
RegExp.$3 // ''
- 前提条件,必须是由括号包裹,括号我们后面会具体介绍
- 可以看到,第一个正则,只匹配到一个括号,则只有$1有值
- 第二个正则,有两个括号
- 则匹配2
6.6 $分组结果TODO
3. 正则表达式的模式
也分为两种:
- 简单模式 ,如/abc/
- 特殊字符模式,如/ab*c/
其中特殊字符模式是我们需要关注的重点,接下里讲详细说明下
4. 简单模式
const reg = /abc/;
console.log(reg.test('a')); // false
console.log(reg.test('ab')); // false
console.log(reg.test('abc')); // true
console.log(reg.test('abcd'));// true
简单模式真的很简单,就是强匹配,有就true,没有就false
5. 特殊字符模式
正则的特殊符号有很多,但也有分类,主要分为五类:
- 断言
- 字符类
- 分组和范围
- 量词
- Unicode转义
5.1. 断言类
5.1.1 ^匹配开头
概念:^匹配开头
const reg = /^A/;
reg.test('AB'); //true
reg.test('BA'); //false
很好理解,就是开头是否以某一个字符开始,但有一种情况需要注意,就是多行文本的时候
const reg = /^A/;
reg.test(`B
A`); // false;
// 正则可以通过/m开启多行模式
const reg = /^A/m;
reg.test(`B
A`);// true
这里有个/m,这是用来标识,是否识别多行的
m属于修饰符,暂时先不展开,后面有大篇幅来讲修饰符
5.1.2 $匹配结尾
概念:$匹配开头
const reg = /A$/;
reg.test('BA') // true
reg.test('AB') // false
同样存在多行模式的匹配情况
const reg = /B$/;
reg.test(`B
A`) // false;
// 正则可以通过/m开启多行模式
const reg = /B$/m;
reg.test(`B
A`) // true;
5.1.3 \b匹配单词边界
概念:\b是用来匹配单词边界的,有点不好理解,看下面的例子
const reg = /A\b/;
reg.test('AB') // false
reg.test('BA') // true
reg.test('ABB') // false;
reg.test('BA abcd') // true
- reg 设置为了,字符A右侧为单词边界
- AB,A右侧不是边界,是B,所以false
- BA,A右侧是边界,所以true
- ABB,A右侧不是边界,是B,所以false
- BA abcd,A右侧是边界空格,所以true
5.1.4 \B匹配单词非边
概念:上面\b用来匹配单词的边,而\B则是专门用来匹配非边的
const reg = /A\B/;
reg.test('AB'); // true
reg.test('BA'); // false
reg.test('BAA');// true
- reg设置为了,字符A右侧为非单词边界
- AB,A右侧不是边界,是B,所以true
- BA,A右侧是边界,所以false
- BAA,A右侧不是边界,是A,所以true
5.1.5 x(?=y)向前断言(实用)
这个向前,是指match方法最后拿到的是x,不是y,所以叫向前断言 概念:x后面紧随y,才能被匹配,很实用
const reg = /A(?=B)/;
reg.test('AB'); // true
reg.test('BA'); // false
reg.test('ABC');// true
reg.test('ACB');// false
- reg设置为了,字符A右侧必须跟随B
- AB,A右侧紧随B,所以true
- BA,A右侧没有紧随B,所以false
- ABC,A右侧紧随B,所以true
- ACB,A右侧没有紧随B,所以false
5.1.6 x(?!y)向前否定断言(实用)
const reg = /A(?!B)/
reg.test('AB') // false
reg.test('BA') // true
reg.test('ABC') // false
reg.test('ACB') // true
- reg设置为了,字符A右侧必须不能跟随B
- AB,A右侧紧随B,所以false
- BA,A右侧没有紧随B,所以true
- ABC,A右侧紧随B,所以false
- ACB,A右侧没有紧随B,所以true
5.1.7 (?<=y)x 向后断言(实用)
概念:x前面必须贴着y,同向前断言,前后的判断依据是x的位置:
- 向前断言,判断条件在后x(?=y)
- 向后断言,判断条件在前(?<=y)x
const reg = /(?<=A)B/;
reg.test('AB') // true
reg.test('BA') // false
reg.test('ABC') // true
reg.test('ACB') // false
- reg设置为了,字符B,前面必须是A,注意此时判断主体换成了B
- AB,B前面贴着A,所以true
- BA,B前面没有A,所以false
- ABC,B前面贴着A,所以true
- ACB,B前面没有紧贴着A,所以false
5.1.8 (?<!y)x 向后否定断言(实用)
概念:x前面必须不能贴着y
const reg = /(?<!A)B/;
reg.test('AB')
reg.test('BA')
reg.test('ABC')
reg.test('ACB')
- reg设置为了,字符B,前面必须不能是A,注意此时判断主体换成了B
- AB,B前面贴着A,所以false
- BA,B前面没有A,所以true
- ABC,B前面贴着A,所以false
- ACB,B前面没有紧贴着A,所以true
5.2 字符类
字符类和断言不一样,断言判断边界,字符则主要是判断内容是啥
5.2.1 . 匹配任意字符,除换行符
const reg = /./g
reg.exec('AB'); // ["A", index: 0, input: "AB", groups: undefined]
reg.exec('AB'); // ["B", index: 1, input: "AB", groups: undefined]
reg.exec('AB'); // null
reg.exec(`
`); // null
- reg设置为全局模式
- reg匹配
.所以会拿到先拿到A - 再拿到B
- 匹配不到了,返回null
- 对换行符匹不能,返回null
5.2.2 \d 匹配任意数字
概念:匹配任意0-9的数字
const reg = /\d/;
'1A'.match(reg); // ["1", index: 0, input: "1A", groups: undefined]
- 设置reg匹配任意数字
- 1A匹配后,返回结果1,下标0,输入1A
5.2.3 \D 匹配任意非数字
概念:匹配任意非0-9的字符
const reg = /\D/;
'1A'.match(reg);
["A", index: 1, input: "1A", groups: undefined]
- 设置reg匹配任意非数字
- 1A匹配后,返回结果A,下标1,输入1A
5.2.4 \w 匹配任意拉丁字母,含字母/数字/下划线(要背)
概念:
- 匹配任意小写字母a-z
- 匹配任意大写字母A-Z
- 匹配任意数字
- 匹配下划线_
const reg = /\w/;
reg.test('a') // true;
reg.test('A') // true;
reg.test('1') // true;
reg.test('_') // true;
---
reg.test('%') // false;
- \w匹配小写a
- \w匹配大写A
- \w匹配下划线
- \w不匹配%
5.2.5 \W 匹配任意非拉丁字母
概念:就是\w的取反,
- 匹配任意非小写字母
- 匹配任意非大写字母
- 匹配任意非数字
- 匹配任意非_
const reg = /\W/;
reg.test('a') // false;
reg.test('A') // false;
reg.test('1') // false;
reg.test('_') // false;
---
reg.test('%') // true;
reg.test('-') // true;
5.2.6 \s 匹配任意空白字符,含换行、空格、tab等
概念:空白字符
- 匹配任意空格
- 匹配任意换行符
- 匹配tab切换
- 匹配回车
const reg = /\s/;
reg.test(' ') // true
reg.test('\n') // true
reg.test(' ') // true
reg.test(`
`) // true
reg.test('1') // false
- 定义reg匹配任意空行操作
- 匹配空格,true
- 匹配换行符,true
- 匹配tab,true
- 匹配换行字符串,true
- 匹配字符串,false
5.2.7 \S 匹配任意非空白字符
概念:匹配任意非空白字符,其实就是有内容的字符
const reg = /\S/;
reg.test(1); // true
reg.test('a'); // true
reg.test('A'); // true
reg.test('%'); // true
---
reg.test(' '); // false
reg.test('\n'); // false
- reg设置匹配非空白符
- 匹配拉丁字母,true
- 匹配运算字符,true
- 匹配空白符,false;
字符类,掌握👆这些就够用了,剩余的就不再继续展开了
5.3 组和范围
概念:组合范围,涵盖所有范围
5.3.1 x|y 或
概念:匹配x或y
const reg = /A|B/
reg.exec('AC') // ["A", index: 0, input: "AC", groups: undefined]
reg.exec('CB') // ["B", index: 1, input: "CB", groups: undefined]
reg.exec('CD') // null
---
const reg = /A|B/g
reg.exec('ABC') // ["A", index: 0, input: "ABC", groups: undefined]
reg.exec('ABC') // ["B", index: 1, input: "ABC", groups: undefined]
reg.exec('ABC') // null
- 非全局模式
- 匹配A或B
- AC通过,true
- CB通过,true
- CD不通过,null
- 全局模式
- ABC,第一次匹配A
- ABC,第二次匹配B
- ABC,匹配不到,返回null
5.3.2 [x-y]指定字符范围
概念:其实同或的概念一致,只是简化了或的写法,可以用包含来理解 例子:前面|的操作匹配0-3的话,得写:0|1|2|3,但用[0-3]则能快速指定字符范围
const reg = /[0-3]/
reg.exec('0') // ["0", index: 0, input: "0", groups: undefined]
reg.exec('1') // ["1", index: 0, input: "1", groups: undefined]
reg.exec('2') // ["2", index: 0, input: "2", groups: undefined]
reg.exec('3') // ["3", index: 0, input: "3", groups: undefined]
reg.exec('4') // null
注意:-只能在中间,例如:
- a-z
- 0-9
如果-在头尾的话,则默认为字符,例如:
- -az
- -09
const reg = /[-az]/;
reg.exec('a'); // ["a", index: 0, input: "a", groups: undefined]
reg.exec('b'); // null
reg.exec('-'); // ["-", index: 0, input: "-", groups: undefined]
可以看出,[xyz]模式比|要灵活很多,方便记忆的话,可以仅使用[xyz]模式
5.3.3 [^x-y] 否定指定字符范围
概念:很好理解,就是对👆指定字符范围否定
const reg = /[^0-3]/
reg.exec('0'); // null
reg.exec('1'); // null
reg.exec('2'); // null
reg.exec('3'); // null
reg.exec('4'); // ["4", index: 0, input: "4", groups: undefined]
- reg设置为,对0-3进行否定
- 所以0、1、2、3都不能匹配
- 4可以匹配
- 其他-的规则同上👆
5.3.4 (x) 分组捕获
概念:通过()小括号来进行分组,返回值会是一个数字,依次返回匹配值,分组结果
const reg = /(.)(.)./;
reg.exec('ABC'); // ["ABC", "A", "B", index: 0, input: "ABC", groups: undefined]
RegExp.$1 // A
RegExp.$2 // B
reg.exec('AB'); // null;
- 定义reg为
.,目的是匹配3个任意字符- 其中两个
.通过括号包裹,定义为分组
- 其中两个
- 匹配ABC,获得返回结果数组,包含3个值
- 匹配值:ABC
- 第一个分组捕获的值:A
- 第二个分组捕获的值:B
发现,分组捕获的值,下标是从1开始的,和$的开始下标一致
5.3.4.1 (x)分组嵌套逻辑
既然能分组,那分组肯定会存在嵌套,嵌套分组的执行逻辑是?
const reg = /((.).(.))((.)(.))/;
reg.exec('ABCDE');
// ["ABCDE", "ABC", "A", "C", "DE", "D", "E", index: 0, input: "ABCDE", groups: undefined]
- reg稍微有点复杂:
- 一共分了两大组
- 左侧大组1
- 小组1:任意字符
- 任意字符
- 小组2:任意字符
- 右侧大组2
- 小组3:任意字符
- 小组4:任意字符
- 左侧大组1
- 一共分了两大组
- 从结果分析
- ABCDE:匹配值
- ABC:左侧大组1匹配结果
- A:大组1中小组1的匹配结果
- C:大组1中小组2的匹配结果
- DE:右侧大组2匹配结果
- D:大组2中小组3的匹配结果
- E:大组2中小组4的匹配结果
结论:可以看到输出是一个深度遍历的过程,先把一个分组拿全,再拿另一个分组
5.3.5 \NUM 反向引用(重点理解)
概念:
- 前面反复讲,匹配结果会放在RegExp的2、$3上;
- 但这都是在匹配后才能拿到,如果想在定义正则表达式的时候就用呢?
- \NUM或者\N的就是用来解决这个问题
语法层面:
- \1 = $1
- \2 = $2
- \3 = $3
- ...
不是很好理解,需要通过案例仔细分析
const reg = /(.)\1/;
reg.exec('A') // null
reg.exec('AB') // null
reg.exec('AA') // ["AA", "A", index: 0, input: "AA", groups: undefined]
- reg定义
- (.):匹配任意字符为第一分组
- \1:意思是将已匹配分组$1的值拿过来直接用
- 匹配A,由于长度不对,null
- 匹配AB
- A匹配为第一分组,所以$1为A
- 此时\1相当于A
- 接下来拿B去比较A,不匹配
- 返回null
- 匹配AA
- A匹配为第一分组,所以$1为A
- 此时\1相当于A
- 接下来拿A去比较A,匹配
- 返回结果
还需要多看几个例子:
5.3.5.1 匹配4个数字,第1个和第3个数字相同
// 匹配4个数字,第1个和第3个数字相同
const reg = /(\d)\d\1\d/;
reg.exec(1234) // null;
reg.exec(1214) // ["1214", "1", index: 0, input: "1214", groups: undefined]
// 匹配4个数字,13相同,24相同
const reg = /(\d)(\d)\1\2/;
reg.exec(1234) // null
reg.exec(1214) // null
reg.exec(1312) // null
reg.exec(1212) // ["1212", "1", "2", index: 0, input: "1212", groups: undefined]
- reg设置匹配4个数字
- (\d)第一个分组,匹配数字
- \d匹配数字
- \1匹配$1,也就是第一个分组
- \d匹配数字
- 1234匹配
- $1是1
- 3和1不相等,所以返回null
- 1214匹配
- $1是1
- 1和1相等,返回
5.3.5.2 匹配4个数字,第13相同,第24相同
// 匹配4个数字,13相同,24相同
const reg = /(\d)(\d)\1\2/;
reg.exec(1234) // null
reg.exec(1214) // null
reg.exec(1312) // null
reg.exec(1212) // ["1212", "1", "2", index: 0, input: "1212", groups: undefined]
- reg设置匹配4个数字
- (\d)第一个分组,匹配数字
- (\d)第二个分组,匹配数字
- \1匹配$1,也就是第一个分组
- \2匹配$2,也就是第二个分组
- 1234匹配
- $1是1
- 3和1不相等,返回null
- 1214匹配
- $2是2
- 4和2不相等,返回null
- 1312匹配
- $2是3
- 3和2不相等,返回null
- 1212匹配
- 2是2
- 第三位1 = 2,全匹配,返回结果
5.3.5.3 匹配4个数字,第12相同,第34相同
const reg = /(\d)\1(\d)\2/;
reg.exec('1234') // null
reg.exec('1233') // null
reg.exec('2233') // ["2233", "2", "3", index: 0, input: "2233", groups: undefined]
- reg设置匹配4个数字
- (\d)第一个分组,匹配数字
- \1匹配$1,也就是第一个分组
- (\d)第二个分组,匹配数字
- \2匹配$2,也就是第二个分组
- 1234匹配
- $1是1
- 2不等于1,返回null
- 1233匹配
- $1是1
- 2不等于1,返回null
- 2233匹配
- $1是2
- 2 = $1
- $2是3
- 3 = $2,全匹配,返回结果
5.3.6 (?) 反向引用 - 具名捕获组
概念:
- 前面反向引用是围绕分组的
- ()括号分组可以嵌套,所以组可以一层套一层,会非常负责,所以有必要为组起名字
语法:
- 起名(?)
- 引用\k
const reg = /(?<num1>\d)(?<num2>\d)/
reg.exec(12); // ["12","1","2",groups: {num1: "1", num2: "2"}]
- 定义reg分了两个组
- 组1名字叫num1
- 组2名字叫num2
- 12进行匹配
- 结果中有了groups,包含num1和num2
5.3.7 \k 反向引用 - 具名引用
上面通过(?)起了名,但怎么用呢? 语法是:
- 起名(?)
- 引用\k
注意这个\k是固定的写法,因为不知道该\1还是\2了,所以\k
const reg = /(?<num>\d)\k<num>/;
reg.exec(12) // null
reg.exec(11) // ["11", "1", index: 0, input: "11", groups: {…}]
- 定义reg为:
- (?\d):分组名为num,匹配规则任意数字
- \k:引用名为num的分组
其实和上面\1的能力一样,只是一个有名字,一个没名字,感觉这个比较难用,语法的干扰比较大
5.3.8 (?:x) 非捕获组
概念:
- 捕获组概念很明确,就是将内容分组,结果都会在匹配结果中输出
- 但这里有个问题,不是每一个分组的结果都有意义
- 所以怎么只保留对有意义的捕获组
(?:x):用来标记不需要在结果中输出
const reg = /(\d)\d/;
reg.exec(12) // ["12", "1", index: 0, input: "12", groups: undefined]
---
const reg = /(?:\d)\d/;
reg.exec(12) // ["12", index: 0, input: "12", groups: undefined]
- 两个reg的区别,仅是分组中添加了(?:x)
- 结果上
- 第一个分组被输出到结果了
- 第二个分组没有被输出
5.4 量词
概念:控制匹配的数量
5.4.1 * 匹配0次或多次
const reg = /AB*/;
reg.exec('A'); // ["A", index: 0, input: "A", groups: undefined]
reg.exec('AB'); // ["AB", index: 0, input: "AB", groups: undefined]
reg.exec('ABB'); // ["ABB", index: 0, input: "ABB", groups: undefined]
reg.exec('ABC'); // ["AB", index: 0, input: "ABC", groups: undefined]
reg.exec('ABBBBC'); // ["ABBBB", index: 0, input: "ABBBBC", groups: undefined]
reg.exec('AC'); // ["A", index: 0, input: "AC", groups: undefined]
- 定义reg:
- 匹配A
- 匹配B个数0个或多个
- A匹配:A通过,B共0个,通过
- AB匹配:A通过,B共1个,通过
- ABB匹配:A通过,B共2个,通过
- ABC匹配:A通过,B共1个,通过
- ABBBC匹配:A通过,B共4个,通过
- AC匹配:A通过,B共0个,通过
注意:0个也是ok的,这一点比较容易被忽略
5.4.2 + 匹配1次或多次
概念:与*唯一的不同,就是+至少要有一个
const reg = /AB+/;
reg.exec('A'); // null
reg.exec('AB'); // ["AB", index: 0, input: "AB", groups: undefined]
reg.exec('ABB'); // ["ABB", index: 0, input: "ABB", groups: undefined]
reg.exec('ABC'); // ["AB", index: 0, input: "ABC", groups: undefined]
reg.exec('ABBBBC'); // ["ABBBB", index: 0, input: "ABBBBC", groups: undefined]
reg.exec('AC'); // null
需要记忆的就一个点,就是+必须要有1个,而*可以是0个
5.4.3 ? 匹配0次或1次
const reg = /AB?/;
reg.exec('A') // ["A", index: 0, input: "A", groups: undefined]
reg.exec('AB') // ["AB", index: 0, input: "AB", groups: undefined]
reg.exec('ABB') // ["AB", index: 0, input: "ABB", groups: undefined]
- 定义reg:
- 匹配A
- 匹配B,0次或1次
- A匹配:A通过,B共0,通过
- AB匹配:A通过,B共1次,通过
- ABB匹配:A通过,B共1次,通过
注意:?的语法下,存在多个B也只会是返回1个B!!!
5.4.3.1 ?的非贪婪行为
前面讲过:
-
- 匹配0或多次
-
- 匹配1或多次
如果组合使用会怎样呢?
const reg = /AB+/
reg.exec('AB') // ["AB", index: 0, input: "AB", groups: undefined]
reg.exec('ABB') // ["ABB", index: 0, input: "ABB", groups: undefined]
reg.exec('ABBB') // ["ABBB", index: 0, input: "ABBB", groups: undefined]
---
const reg = /AB+?/
reg.exec('AB') // ["AB", index: 0, input: "AB", groups: undefined]
reg.exec('ABB') // ["AB", index: 0, input: "ABB", groups: undefined]
reg.exec('ABBB') // ["AB", index: 0, input: "ABBB", groups: undefined]
- reg设置为了AB+,B会匹配1次或多次
- 从结果上来看,匹配到的结果也确实如此
- reg设置为了AB+?,B就变成非贪婪了
- 从结果上能够看到,B仅匹配了1次
这个概念不是很实用,关注下就好
5.4.4 x{n} 匹配n次
前面讲的,都是匹配0次、1次、多次,并不能很灵活的定制,例如想匹配2次、3次、4次怎么办? 概念:x{n} 就是为了解决这个问题,n匹配的是前面的规则,不是前面的字符,这一点必须明确
const reg = /A{2}/
reg.exec('A') // null
reg.exec('AB') // null
reg.exec('AA') // ["AA", index: 0, input: "AA", groups: undefined]
---
const reg = /.{2}/
reg.exec('A'); // null
reg.exec('AB'); // ["AB", index: 0, input: "AB", groups: undefined]
reg.exec('AC'); // ["AC", index: 0, input: "AC", groups: undefined]
- 设置reg为A个数2个
- A匹配:A有1个,不通过
- AB匹配:A有1个,不通过
- AA匹配:A有2个,通过
- 设置reg为任意字符2个
- A匹配:1个字符,不通过
- AB匹配:2个字符,通过
- AC匹配:2个字符,通过
注意:
- {n}必须是连续的
- {n}看第二个例子匹配
.任意字符,两个字符是否相同并不关键,关键的是匹配规则是否相同
5.4.5 x{n,} 匹配至少n次
上面的x{n}是强制匹配n次,但无法实现,至少匹配多少次 概念:x{n, }的能力,就是至少匹配多少次
const reg = /A{3,}/;
reg.exec('A') // null
reg.exec('AA') // null
reg.exec('AAA') // ["AAA", index: 0, input: "AAA", groups: undefined]
reg.exec('AAAA') // ["AAAA", index: 0, input: "AAAA", groups: undefined]
- 定义reg:为A匹配至少3次
- A匹配:A共1个,不通过
- AA匹配:A共2个,不通过
- AAA匹配:A共3个,通过
- AAAA匹配:A共4个,通过
5.4.6 x{n,m}匹配n - m次
const reg = /A{0,2}/
reg.exec('B') // ["", index: 0, input: "B", groups: undefined]
reg.exec('A') // ["A", index: 0, input: "A", groups: undefined]
reg.exec('AA') // ["AA", index: 0, input: "AA", groups: undefined]
reg.exec('AAA') // ["AA", index: 0, input: "AAA", groups: undefined]
- 定义reg为匹配A字符0-2次
- B匹配:A0次,通过,返回了"",这个要格外注意
- A匹配:A1次,通过
- AA匹配:A2次,通过
- AAA匹配:A3次,通过,但匹配结果只返回AA
5.5 修饰符
概念:修饰符为正则添加全局规则
5.5.1 g 全局搜索
const reg = /A/g;
[...'ABA'.matchAll(reg)];
//[
// ["A", index: 0, input: "ABA", groups: undefined],
// ["A", index: 2, input: "ABA", groups: undefined
//]
可以看到,通过/g一口气全都匹配出来了
5.5.2 i 不区分大小写
const reg = /A/i;
reg.exec('A') // ["A", index: 0, input: "A", groups: undefined]
reg.exec('a') // ["a", index: 0, input: "a", groups: undefined]
很好理解,设置i后,大小写都能匹配
5.5.3 m 多行搜索
const reg = /^A/
reg.exec(`
A`) // null
---
const reg = /^A/m
reg.exec(`
A`) // ["A", index: 1, input: "\nA", groups: undefined]
也很好理解,通过m可以将文字拆解为每行进行匹配
5.5.4 s 允许.匹配换行符(空白符)
const reg = /./;
reg.exec('\n') // null
---
const reg = /./s
reg.exec('\n') // ["\n", index: 0, input: "\n", groups: undefined]
没有设置s前,无法匹配空白字符,设置s后,就可以正常匹配换行符了
5.5.5 y 粘性匹配(不好理解)
前面讲过g是全局匹配,通过exec可以获得一个属性叫做lastIndex,我们再看下
const reg = /A/g
reg.exec('AA A'); // ["A", index: 0, input: "AA A", groups: undefined]
reg.lastIndex; // 1
reg.exec('AA A'); // ["A", index: 1, input: "AA A", groups: undefined]
reg.lastIndex; // 2
reg.exec('AA A'); // ["A", index: 3, input: "AA A", groups: undefined]
reg.lastIndex; // 4
reg.exec('AA A'); // null
reg.lastIndex; // 0
- 设置reg为全局匹配A
- AA A匹配第一次:
- 通过,匹配了下标0的位置,标记下一次开始lastIndex为1
- AA A匹配第二次:
- 通过,匹配了下标1的位置,标记下一次开始lastIndex为2
- AA A匹配第三次:
- 通过,匹配了下标3的位置,标记下一次开始lastIndex为4
可以看到,第二次匹配时,标记下一次开始的lastIndex为位置2,但第三次匹配时,找到的结果位置在3 这说明,g的全局搜索模式,在第三次搜索时,自动向后搜索了,lastIndex的2位置没找到,就向后走到了3
但这么做有个问题,当文章内容过多时,会一路走到尾吧 /y的目的就是,严格按照lastIndex进行匹配
const reg = /A/y
reg.lastIndex; // 0
reg.exec('AA A'); // ["A", index: 0, input: "AA A", groups: undefined]
reg.lastIndex; // 1
reg.exec('AA A'); // ["A", index: 1, input: "AA A", groups: undefined]
reg.lastIndex; // 2
reg.exec('AA A'); // null
reg.lastIndex; // 0
- reg设置粘性匹配
- AA A匹配第一次,从lastIndex = 0开始,严格匹配,不向后移动:
- 通过,匹配了下标0的位置,标记下一次开始lastIndex为1
- AA A匹配第二次,从lastIndex = 1开始,严格匹配,不向后移动:
- 通过,匹配了下标1的位置,标记下一次开始lastIndex为2
- AA A匹配第三次,从lastIndex = 2开始,严格匹配,不向后移动:
- 下标2是空格,不匹配,返回null
其实y和g的最本质区别是:
- y按照lastIndex严格匹配,匹配不到就结束
- g在lastIndex没匹配到也会向后继续匹配
还可以看个最简单的例子:
const reg = /A/g
reg.lastIndex; // 0
reg.exec('BA'); // ["A", index: 1, input: "BA", groups: undefined]
---
const reg = /A/y
reg.lastIndex; // 0
reg.exec('BA'); // null