正则原理剖析

一、 什么是回溯?

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

没有回溯

目标字符串是"abbbc"时,就没有所谓的“回溯”,匹配过程如下:
image.png

有回溯

如果目标字符串是"abbc",中间就有回溯。匹配过程如下:
image.png
图中第五步表示匹配失败,此时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) (?<!exp):零宽负向后行断言
    let reg = /(?<!prefix)(test)/ // 匹配前面不是prefix的test

    零宽是何意?

    零宽断言是一种零宽度的匹配,它匹配到的内容不会保存到匹配的结果中去,最终匹配的只是一个位置,这个位置的结果代表是否。

    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 = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
    let ret = str.match(reg).groups
    输出结果:{year: '2021', month: '09', day: '26'}

四、 实战分析

Input.Password密码输入组件,密码的规则是满足四种字符中的至少三种字符类型,如下图:
image.png
将表达式中的\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,}$");

2 声望
2 粉丝
0 条评论
推荐阅读
「多图预警」完美实现一个@功能
一天产品大大向 boss 汇报完研发成果和产品业绩产出,若有所思的走出来,劲直向我走过来,嘴角微微上扬。产品大大:boss 对我们的研发成果挺满意的,balabala...(内心 OS:不听,讲重点)产品大大:咱们的客服 I...

wuwhs40阅读 4.8k评论 5

封面图
ESlint + Stylelint + VSCode自动格式化代码(2023)
安装插件 ESLint,然后 File -&gt; Preference-&gt; Settings(如果装了中文插件包应该是 文件 -&gt; 选项 -&gt; 设置),搜索 eslint,点击 Edit in setting.json

谭光志34阅读 20.7k评论 9

涨姿势了,有意思的气泡 Loading 效果
今日,群友提问,如何实现这么一个 Loading 效果:这个确实有点意思,但是这是 CSS 能够完成的?没错,这个效果中的核心气泡效果,其实借助 CSS 中的滤镜,能够比较轻松的实现,就是所需的元素可能多点。参考我们...

chokcoco24阅读 2.2k评论 3

你可能不需要JS!CSS实现一个计时器
CSS现在可不仅仅只是改一个颜色这么简单,还可以做很多交互,比如做一个功能齐全的计时器?样式上并不复杂,主要是几个交互的地方数字时钟的变化开始、暂停操作重置操作如何仅使用 CSS 来实现这样的功能呢?一起...

XboxYan25阅读 1.7k评论 1

封面图
在前端使用 JS 进行分类汇总
最近遇到一些同学在问 JS 中进行数据统计的问题。虽然数据统计一般会在数据库中进行,但是后端遇到需要使用程序来进行统计的情况也非常多。.NET 就为了对内存数据和数据库数据进行统一地数据处理,发明了 LINQ (L...

边城17阅读 2k

封面图
【代码鉴赏】简单优雅的JavaScript代码片段(一):异步控制
Promise.race不满足需求,因为如果有一个Promise率先reject,结果Promise也会立即reject;Promise.all也不满足需求,因为它会等待所有Promise,并且要求所有Promise都成功resolve。

csRyan26阅读 3.3k评论 1

「彻底弄懂」this全面解析
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在 哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在 函数执行的过程中用到...

wuwhs17阅读 2.4k

封面图
2 声望
2 粉丝
宣传栏