正则原理剖析

一、 什么是回溯? 回溯法也称试探法,它的基本思想是:从问题的某一种状态(初始状态)出发,搜索从这种状态出发所能达到的所有“状态”,当一条路走到“尽头”的时候(不能再前进),再后退一步或若干步,从另一种可能“状态”出发,继续搜索,直到所有的“路径”(状态)都试探过。这种不断“前进”、不断“回溯”寻找解的方法,就称作“回溯法”。
假如我们的正则为 /ab{1,3}c/,其可视化形式是:
正则原理剖析
文章图片

没有回溯 目标字符串是"abbbc"时,就没有所谓的“回溯”,匹配过程如下:
正则原理剖析
文章图片

有回溯 如果目标字符串是"abbc",中间就有回溯。匹配过程如下:
正则原理剖析
文章图片

图中第五步表示匹配失败,此时b{1,3}已经匹配到了两个b,当往第六步走的时候发现后面是个‘c’,那么此时断定b{1,3}已经匹配完成,回到之前的状态第四步,这一过程称之为回溯,这只是回溯的一种体现。
二、 JS正则原理 1、 DFA-确定有穷状态自动机-Deterministic finite automaton 特点:文本主导
DFA读入一个文本时,会记录当前表达式中所有满足匹配要求的位置,这些位置集合对应于DFA的一个状态
2、 NFA-非确定有穷状态自动机-No-deterministic finite auotmaton 特点:表达式主导
从表达式的左边开始,每次读取一个正则表达式的一个字符,检查当前文本是否匹配当前表达式,如果是则继续正则的下一个字符。
3、示例分析 正则:reg=to(nite|knight|night) 字符串:tonight
DFA匹配过程:
1、 当引擎读入文本t,记录匹配位置to(nite|knight|night)
2、 读入文本o,记录位置to(nite|knight|night)
3、 读入文本n,记录位置to(nite|knight|night) // 体现与NFA的不同之处
...
NFA匹配过程:
1、 获取表达式的to(nite|knight|night),扫描匹配字符串中的t
2、 获取表达式的to(nite|knight|night),扫描匹配字符串中的to
3、 括号中的表达式分三种情况,引擎会尝试三种情况
。。。
从示例中可以得到结论:
DFA每一次匹配会记录所有满足要求的表达式位置,组成集合,当往下面匹配时有不满足要求的集合将会被剔除,每一步都是有效的匹配,所以不存在回溯。
NFA遇到具有选择不确定性的表达式会进行试探匹配,顺着分支往下匹配,如果后续有不匹配的,那么会重新回到确定性的位置,继续下一个分支进行匹配,要回到原来的位置的前提条件就是需要记住历史状态,所以NFA具有回溯。
三、 高级功能
NFA除了支持回溯之外,还支持分组,捕获,零宽断言等高级功能,而这些功能的基础是NFA具有子表达式独立匹配的特点。
1、分组 将一个或多个字符使用括号包裹起来组成一个整体,分组分为捕获组和非捕获组。
捕获组: 示例(ABC)(ABCD(ABCDE)),存在四个分组:

内容
分组1 (ABC)(ABCD(ABCDE))
分组2 (ABC)
分组3 (ABCD(ABCDE))
分组4 (ABCDE)
捕获组的应用:
  • 反向引用
  • 计数
    注意点:
    反向引用,引用的仅仅是文本内容,而不是正则表达式。
    反向引用中为什么我们一般是使用的分组是从\1开始的?
    非捕获组:以 (? 开始的组就是非捕获组,不将匹配到的字符存储到内存中,从而节省内存。
    (?:a|A)123(?:b)可以匹配a123b或者A123b
    非捕获组有很多种形式,其中包含零宽断言、模式修正符等。环视又称环视或预搜索
    零宽断言,常见的零宽断言有四种:
    a) (?=exp):零宽正向先行断言
    let reg = /(test)(?=suffix)/ // 匹配后面为suffix的test
    b) (?<=exp):零宽正向后行断言
    let reg = /(?<=prefix)(test)/ //匹配前面为prefix的test
    c) (?!exp):零宽负向先行断言
    let reg = /(test)(?!suffix)/ // 匹配后面不是suffix的test
    d) (?let reg = /(? 零宽是何意?零宽断言是一种零宽度的匹配,它匹配到的内容不会保存到匹配的结果中去,最终匹配的只是一个位置,这个位置的结果代表是否。
    2、模式修正符:一般情况下我们会在表达式的后面加上g,i,m,s对整个表达式进行修饰,js正则表达式还支持对局部表达式进行修饰的用法。
    (?i)AB 对表达式(?i)后面的字符开启不区分大小写的限制,可以匹配:ab、aB、Ab、AB
    (?i:a)b 只对a不区分大小写,可以匹配:Ab、ab
    3、捕获ES6正则命名捕获,不需要纠结分组的下标,根据定义正则表达式对分组的名称进行访问。
    示例:
    let str = '2021-09-26'
    let reg = /(?\d{4})-(?\d{2})-(?\d{2})/
    let ret = str.match(reg).groups
    输出结果:{year: '2021', month: '09', day: '26'}
四、 实战分析 【正则原理剖析】Input.Password密码输入组件,密码的规则是满足四种字符中的至少三种字符类型,如下图:
正则原理剖析
文章图片

将表达式中的\W替换为“()!@#$%^&*|?><”即可
new RegExp("^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\W]+$)(?![a-z0-9]+$)(?![a-z\W]+$)(?![0-9\W]+$)[a-zA-Z0-9\W]{8,}$");

    推荐阅读