前端正则,拿来吧你!

840 阅读15分钟

1. 正则表达式的创建

正则表达式有两种创建方法

  1. 通过/abc/,仅支持静态正则
  2. 通过new RegExp('abc'),支持动态正则

推荐使用第二种,采用new RegExp()模式,优点在于动态正则,本质是因为字符串可拼接


2. 正则表达式的方法

在了解具体的正则配置之前,有必要先掌握清楚正则每个方法的作用,这样才能够帮助更好的理解正则 可用于正则的一共有两种数据类型

  1. RegExp对象方法
  2. 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
  1. reg的test方法每次匹配的位置是不一样的
  2. lastIndex表示后续匹配将从lastIndex之后开始
  3. 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
  1. reg设置为了AB,全局g匹配
  2. reg匹配str ='DCAB ABCD CABD';
    1. 返回结果,包含匹配值,下标等
    2. reg对象的lastIndex,更新为下一次匹配的开始位置4
  3. reg再次匹配str
    1. 再次返回结果
    2. reg对象的lastIndex,更新为下一次匹配的开始位置7
  4. reg再次匹配str
    1. 再次返回结果
    2. reg对象的lastIndex,更新为下一次匹配的开始位置13
  5. reg再次匹配str
    1. 匹配不到了,返回null
    2. reg对象的lastIndex重置为0

RegExp的方法一共就两个,都可以反复调用

  1. 常规用test就够
  2. 想精细化操作用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;
  1. 此时字符串是主体
  2. AB检查是否匹配A,得到下标0;
  3. BA检查是否匹配A,得到下标1;
  4. C检查是否匹配A,未找到,返回-1;
  5. AB AB检查是否匹配A,得到下标0;
  6. AB AB检查是否匹配A,依旧得到第一个检索到的下标0;

search不能进行迭代查询,不能判断有多少个匹配,使用场景受限:

  1. 仅需要知道是否匹配(test也满足)
  2. 且要获取第一个匹配的下标(这一点比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]
---
  1. reg设置匹配A
  2. AB进行匹配,匹配到A,返回结果的相关信息
  3. BA进行匹配,匹配到A,返回结果的相关信息
  4. 创建str包含重复的A
  5. str进行匹配,匹配到第一个A,返回结果相关信息
  6. str进行匹配,还是匹配到第一个A,返回结果相关信息

match方法,也不能进行迭代查询,仅能拿到匹配的第一个结果,返回值和exec相同 有一点需要注意,就是设置/g全局匹配时

const reg = /A/g;
'AB AB'.match(reg); //  ["A", "A"]
  1. reg全局匹配A
  2. str进行match后,返回所有符合的结果集合
  3. 注意:此结果集不包含下标信息等内容,仅包含匹配结
    1. 只能用在快速得到有多少个符合的结果上

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. 参数1:可以是正则,也可以是固定的字符串
  2. 参数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
  1. 支持纯静态字符串A->C
  2. 支持reg形式的正则替换
  3. 支持函数动态处理
  4. 每一个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
  1. 匹配到A,替换为美元符号,发现是两个$$,会替换成一个$
  2. 匹配到A,替换为美元符号,发现是三个$$$,其中两个会替换成一个,所以结果是,所以结果是+$ = $$;
  3. 匹配到A,替换为美元符号,发现是四个$$$$,其中两个会替换成一个$,所以结论是$$;

上面👆这个了解就好,不用深入


6.2 $&最后匹配到的字符串

概念:$&代表匹配到的结果

'AB'.replace(/A/,'C')  // CB
RegExp.lastMatch       // A
RegExp['$&']           // A
'AB'.replace(/A/,'$&') // AB
  1. AB通过正则匹配A,替换成C,输出CB
  2. 注意:全局对象RegExp上lastMatch的值更新为了,匹配结果A
  3. 同时RegExp上的[$&]也变成了A
  4. 在参数字符串中使用$&,也是同样的效果

结论:不推荐,因为RegExp是全局对象,任意一个正则触发都会影响到RegExp对象,安全性极低


6.3 $`最后匹配结果的左侧内容

概念:同上面的概念相似,只是变为了匹配结果的左侧

'CBA'.replace(/A/,'D');  // CBD
RegExp.leftContext;      // CB
RegExp['$`'];            // CB
'CBA'.replace(/A/,'$`'); // CBCB
  1. CBA通过正则/A/匹配,替换内容为D,结果为CBD
  2. 注意:全局对象RegExp上leftContext的值更新为了,匹配结果A的左侧内容CB
  3. 同时RegExp上的[$`]也变成了CB
  4. 在参数字符串中使用$`,就输出了结果CBCB

6.4 $'最后匹配结果的右侧内容

概念:和上面的正好相反

'ABC'.replace(/A/,'D');   //DBC
RegExp.rightContext       //BC
RegExp['$\'']             //BC
'ABC'.replace(/A/,'$\''); //BCBC
  1. ABC通过正则/A/匹配,替换内容为D,结果为DBC
  2. 注意:全局对象RegExp上rightContext的值更新为了,匹配结果A的右侧内容BC
  3. 同时RegExp上的[$']也变成了BC
  4. 在参数字符串中使用$',就输出了结果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. 可以看到,第一个正则,只匹配到一个括号,则只有$1有值
  3. 第二个正则,有两个括号
    1. 则匹配11和2

6.6 $分组结果TODO


3. 正则表达式的模式

也分为两种:

  1. 简单模式 ,如/abc/
  2. 特殊字符模式,如/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. 特殊字符模式

正则的特殊符号有很多,但也有分类,主要分为五类:

  1. 断言
  2. 字符类
  3. 分组和范围
  4. 量词
  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

  1. reg 设置为了,字符A右侧为单词边界
  2. AB,A右侧不是边界,是B,所以false
  3. BA,A右侧是边界,所以true
  4. ABB,A右侧不是边界,是B,所以false
  5. 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
  1. reg设置为了,字符A右侧为非单词边界
  2. AB,A右侧不是边界,是B,所以true
  3. BA,A右侧是边界,所以false
  4. 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
  1. reg设置为了,字符A右侧必须跟随B
  2. AB,A右侧紧随B,所以true
  3. BA,A右侧没有紧随B,所以false
  4. ABC,A右侧紧随B,所以true
  5. 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
  1. reg设置为了,字符A右侧必须不能跟随B
  2. AB,A右侧紧随B,所以false
  3. BA,A右侧没有紧随B,所以true
  4. ABC,A右侧紧随B,所以false
  5. ACB,A右侧没有紧随B,所以true

5.1.7 (?<=y)x 向后断言(实用)

概念:x前面必须贴着y,同向前断言,前后的判断依据是x的位置:

  1. 向前断言,判断条件在后x(?=y)
  2. 向后断言,判断条件在前(?<=y)x
const reg = /(?<=A)B/;
reg.test('AB')  // true
reg.test('BA')  // false
reg.test('ABC') // true
reg.test('ACB') // false
  1. reg设置为了,字符B,前面必须是A,注意此时判断主体换成了B
  2. AB,B前面贴着A,所以true
  3. BA,B前面没有A,所以false
  4. ABC,B前面贴着A,所以true
  5. 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')
  1. reg设置为了,字符B,前面必须不能是A,注意此时判断主体换成了B
  2. AB,B前面贴着A,所以false
  3. BA,B前面没有A,所以true
  4. ABC,B前面贴着A,所以false
  5. 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
  1. reg设置为全局模式
  2. reg匹配. 所以会拿到先拿到A
  3. 再拿到B
  4. 匹配不到了,返回null
  5. 对换行符匹不能,返回null

5.2.2 \d 匹配任意数字

概念:匹配任意0-9的数字

const reg = /\d/;
'1A'.match(reg); // ["1", index: 0, input: "1A", groups: undefined]
  1. 设置reg匹配任意数字
  2. 1A匹配后,返回结果1,下标0,输入1A

5.2.3 \D 匹配任意非数字

概念:匹配任意非0-9的字符

const reg = /\D/;
'1A'.match(reg);
["A", index: 1, input: "1A", groups: undefined]
  1. 设置reg匹配任意非数字
  2. 1A匹配后,返回结果A,下标1,输入1A

5.2.4 \w 匹配任意拉丁字母,含字母/数字/下划线(要背)

概念:

  1. 匹配任意小写字母a-z
  2. 匹配任意大写字母A-Z
  3. 匹配任意数字
  4. 匹配下划线_
const reg = /\w/;
reg.test('a') // true;
reg.test('A') // true;
reg.test('1') // true;
reg.test('_') // true;
---
reg.test('%') // false;
  1. \w匹配小写a
  2. \w匹配大写A
  3. \w匹配下划线
  4. \w不匹配%

5.2.5 \W 匹配任意非拉丁字母

概念:就是\w的取反,

  1. 匹配任意非小写字母
  2. 匹配任意非大写字母
  3. 匹配任意非数字
  4. 匹配任意非_
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等

概念:空白字符

  1. 匹配任意空格
  2. 匹配任意换行符
  3. 匹配tab切换
  4. 匹配回车
const reg = /\s/;
reg.test(' ')    // true
reg.test('\n')   // true
reg.test('    ') // true
reg.test(`
`) // true
reg.test('1')    // false
  1. 定义reg匹配任意空行操作
  2. 匹配空格,true
  3. 匹配换行符,true
  4. 匹配tab,true
  5. 匹配换行字符串,true
  6. 匹配字符串,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
  1. reg设置匹配非空白符
  2. 匹配拉丁字母,true
  3. 匹配运算字符,true
  4. 匹配空白符,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
  1. 非全局模式
    1. 匹配A或B
    2. AC通过,true
    3. CB通过,true
    4. CD不通过,null
  2. 全局模式
    1. ABC,第一次匹配A
    2. ABC,第二次匹配B
    3. 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

注意:-只能在中间,例如:

  1. a-z
  2. 0-9

如果-在头尾的话,则默认为字符,例如:

  1. -az
  2. -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]
  1. reg设置为,对0-3进行否定
  2. 所以0、1、2、3都不能匹配
  3. 4可以匹配
  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;
  1. 定义reg为.,目的是匹配3个任意字符
    1. 其中两个.通过括号包裹,定义为分组
  2. 匹配ABC,获得返回结果数组,包含3个值
    1. 匹配值:ABC
    2. 第一个分组捕获的值:A
    3. 第二个分组捕获的值: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]
  1. reg稍微有点复杂:
    1. 一共分了两大组
      1. 左侧大组1
        1. 小组1:任意字符
        2. 任意字符
        3. 小组2:任意字符
      2. 右侧大组2
        1. 小组3:任意字符
        2. 小组4:任意字符
  2. 从结果分析
    1. ABCDE:匹配值
    2. ABC:左侧大组1匹配结果
    3. A:大组1中小组1的匹配结果
    4. C:大组1中小组2的匹配结果
    5. DE:右侧大组2匹配结果
    6. D:大组2中小组3的匹配结果
    7. E:大组2中小组4的匹配结果

结论:可以看到输出是一个深度遍历的过程,先把一个分组拿全,再拿另一个分组


5.3.5 \NUM 反向引用(重点理解)

概念:

  1. 前面反复讲,匹配结果会放在RegExp的11、2、$3上;
  2. 但这都是在匹配后才能拿到,如果想在定义正则表达式的时候就用呢?
  3. \NUM或者\N的就是用来解决这个问题

语法层面:

  1. \1 = $1
  2. \2 = $2
  3. \3 = $3
  4. ...

不是很好理解,需要通过案例仔细分析

const reg = /(.)\1/;
reg.exec('A')  // null
reg.exec('AB') // null
reg.exec('AA') // ["AA", "A", index: 0, input: "AA", groups: undefined]
  1. reg定义
    1. (.):匹配任意字符为第一分组
    2. \1:意思是将已匹配分组$1的值拿过来直接用
  2. 匹配A,由于长度不对,null
  3. 匹配AB
    1. A匹配为第一分组,所以$1为A
    2. 此时\1相当于A
    3. 接下来拿B去比较A,不匹配
    4. 返回null
  4. 匹配AA
    1. A匹配为第一分组,所以$1为A
    2. 此时\1相当于A
    3. 接下来拿A去比较A,匹配
    4. 返回结果

还需要多看几个例子:


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]
  1. reg设置匹配4个数字
    1. (\d)第一个分组,匹配数字
    2. \d匹配数字
    3. \1匹配$1,也就是第一个分组
    4. \d匹配数字
  2. 1234匹配
    1. $1是1
    2. 3和1不相等,所以返回null
  3. 1214匹配
    1. $1是1
    2. 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]
  1. reg设置匹配4个数字
    1. (\d)第一个分组,匹配数字
    2. (\d)第二个分组,匹配数字
    3. \1匹配$1,也就是第一个分组
    4. \2匹配$2,也就是第二个分组
  2. 1234匹配
    1. $1是1
    2. 3和1不相等,返回null
  3. 1214匹配
    1. $2是2
    2. 4和2不相等,返回null
  4. 1312匹配
    1. $2是3
    2. 3和2不相等,返回null
  5. 1212匹配
    1. 111是1,2是2
    2. 第三位1 = 1,第四位2=1,第四位2 = 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]
  1. reg设置匹配4个数字
    1. (\d)第一个分组,匹配数字
    2. \1匹配$1,也就是第一个分组
    3. (\d)第二个分组,匹配数字
    4. \2匹配$2,也就是第二个分组
  2. 1234匹配
    1. $1是1
    2. 2不等于1,返回null
  3. 1233匹配
    1. $1是1
    2. 2不等于1,返回null
  4. 2233匹配
    1. $1是2
    2. 2 = $1
    3. $2是3
    4. 3 = $2,全匹配,返回结果

5.3.6 (?) 反向引用 - 具名捕获组

概念:

  1. 前面反向引用是围绕分组的
  2. ()括号分组可以嵌套,所以组可以一层套一层,会非常负责,所以有必要为组起名字

语法:

  1. 起名(?)
  2. 引用\k
const reg = /(?<num1>\d)(?<num2>\d)/
reg.exec(12); // ["12","1","2",groups: {num1: "1", num2: "2"}]
  1. 定义reg分了两个组
    1. 组1名字叫num1
    2. 组2名字叫num2
  2. 12进行匹配
    1. 结果中有了groups,包含num1和num2

5.3.7 \k 反向引用 - 具名引用

上面通过(?)起了名,但怎么用呢? 语法是:

  1. 起名(?)
  2. 引用\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: {…}]
  1. 定义reg为:
    1. (?\d):分组名为num,匹配规则任意数字
    2. \k:引用名为num的分组

其实和上面\1的能力一样,只是一个有名字,一个没名字,感觉这个比较难用,语法的干扰比较大


5.3.8 (?:x) 非捕获组

概念:

  1. 捕获组概念很明确,就是将内容分组,结果都会在匹配结果中输出
  2. 但这里有个问题,不是每一个分组的结果都有意义
  3. 所以怎么只保留对有意义的捕获组

(?: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]
  1. 两个reg的区别,仅是分组中添加了(?:x)
  2. 结果上
    1. 第一个分组被输出到结果了
    2. 第二个分组没有被输出

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]
  1. 定义reg:
    1. 匹配A
    2. 匹配B个数0个或多个
  2. A匹配:A通过,B共0个,通过
  3. AB匹配:A通过,B共1个,通过
  4. ABB匹配:A通过,B共2个,通过
  5. ABC匹配:A通过,B共1个,通过
  6. ABBBC匹配:A通过,B共4个,通过
  7. 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]
  1. 定义reg:
    1. 匹配A
    2. 匹配B,0次或1次
  2. A匹配:A通过,B共0,通过
  3. AB匹配:A通过,B共1次,通过
  4. 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]
  1. reg设置为了AB+,B会匹配1次或多次
    1. 从结果上来看,匹配到的结果也确实如此
  2. reg设置为了AB+?,B就变成非贪婪了
    1. 从结果上能够看到,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]
  1. 设置reg为A个数2个
  2. A匹配:A有1个,不通过
  3. AB匹配:A有1个,不通过
  4. AA匹配:A有2个,通过
  5. 设置reg为任意字符2个
  6. A匹配:1个字符,不通过
  7. AB匹配:2个字符,通过
  8. AC匹配:2个字符,通过

注意:

  1. {n}必须是连续的
  2. {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]
  1. 定义reg:为A匹配至少3次
  2. A匹配:A共1个,不通过
  3. AA匹配:A共2个,不通过
  4. AAA匹配:A共3个,通过
  5. 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]
  1. 定义reg为匹配A字符0-2次
  2. B匹配:A0次,通过,返回了"",这个要格外注意
  3. A匹配:A1次,通过
  4. AA匹配:A2次,通过
  5. 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
  1. 设置reg为全局匹配A
  2. AA A匹配第一次:
    1. 通过,匹配了下标0的位置,标记下一次开始lastIndex为1
  3. AA A匹配第二次:
    1. 通过,匹配了下标1的位置,标记下一次开始lastIndex为2
  4. AA A匹配第三次:
    1. 通过,匹配了下标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
  1. reg设置粘性匹配
  2. AA A匹配第一次,从lastIndex = 0开始,严格匹配,不向后移动:
    1. 通过,匹配了下标0的位置,标记下一次开始lastIndex为1
  3. AA A匹配第二次,从lastIndex = 1开始,严格匹配,不向后移动:
    1. 通过,匹配了下标1的位置,标记下一次开始lastIndex为2
  4. AA A匹配第三次,从lastIndex = 2开始,严格匹配,不向后移动:
    1. 下标2是空格,不匹配,返回null

其实y和g的最本质区别是:

  1. y按照lastIndex严格匹配,匹配不到就结束
  2. 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

结语:正则掌握到这里就算ok了,以上信息均来源mdn