前言:本文不是入门教程,需要先自行了解基础概念
首先要做的事情是:吐槽
- 难写难读,不易维护: 和医生的处方,道士的符箓并称为天书3贱客!
- 有了错误,无法调试: 无法debugger的痛苦,谁人能懂!
- 执行效率,经常低下: 效率不如普通写法,要你何用!
* 那么,本文到此就结束了,大家各回各家各找各妈吧!
开玩笑~(≧▽≦)/~啦啦啦,那么,正则的意义何在呢?
- 优化代码
- 不得不用
- 装逼利器
下面我们用几个实际例子来说明问题
1、数字金融化(每隔3位逗号分隔,小数保留2位)
普通写法:
12345678910111213(num => {var num = num.toFixed(2);let arr = num.split('.');let inte = arr[0],deci = arr[1],result = '';while (inte.length > 3) {result = ',' + inte.slice(-3) + result;inte = inte.slice(0, inte.length - 3);}result = inte + result + '.' + deci;return result;})(num);正则写法
1num.toFixed(2).replace(/\B(?=(\d{3})+\b)/g,',')
来来,对比下!性能差距不大,但是代码简化了很多有木有!!!
接下去看看这个正则中用到的几个关键字符(\B,?=,\b)
这几个字符在正则中统称为“锚”,概念如下:
锚:相邻字符之间的位置
什么意思呢?看图吧(箭头所指即为锚,不代表任何实际字符):
那么在正则中有哪些字符可以来表示锚
- ^: 匹配开头,在多行匹配中匹配行开头
- $: 匹配结尾,在多行匹配中匹配行结尾
- \b: 单词边界 ,(\w和\W)(\w和$)(\w和^) 之间的位置
- \B: 非单词边界 ,(\w和\w)(\W和\W)(\W和$)(\W和^) 之间的位置
- (?=p): 正向先行断言
- (?!p): 负向先行断言
- (?<=p): 环视 正向先行断言(es5+)
- (?<!p): 环视负向先行断言(es5+)
前面4个不做说明了,我们看看后面4个断言
断言:翻译下来就是说一个判断的言论,那么对于”?=p” 而言”?=”就是一个断言位置,断言这个位置之后肯定是一个p(字符串),换言之:所有p字符换前面的位置都满足这个断言条件.
举个例子来说明:字符串”abc”, “?=bc” 就能匹配到 a和b之间的位置
(?!p)正好和(?=p)相反,他断言这个位置之后肯定不会出现字符串p
继续举个例子: 字符换”abc”, “?!bc” 则能匹配到 a之前的位置,b和c的位置,以及c之后的位置,刚好和”?=bc”是相反集合
(?<=p)和(?<!p) 是前面2个基本是一样的,只不过之前是断言位置后面的信息,而这2个是断言位置前的信息。
看完图也就一目了然了
现在回到我们之前的问题:
- 分解需求:数字从各位开始,每隔3位增加一个逗号
- 确定方案:就是找到合适的锚,然后用逗号将其替换
然后一步步推导出我们的结果:
举一反三:同理如何处理电话号码344分割?银行卡号444…分割?密码强度的验证?
让我们再来看一个例子
如何匹配出文章中所有的日期(年月日)?
先来分解下这个需求,年月日按照习惯可以写成yyyy/mm/dd;yyyy-mm-dd;yyyy.mm.dd等等(中文年月日先不管)
那么除了用正则去匹配,我们也没有想到其他更好的办法,来吧!
是不是觉得太多重复代码了,把重复部分合并一下
合并完,测试了下”2017-12/12”这种不伦不类的居然也匹配上了,只能再改写下了
大家发现了,神奇的”\1”消除了之前的bug
上概念:引用之前出现的分组(括号)用\n来表示,即为反向引用
反向引用非常的好理解,用\n来匹配正则前面第n个括号里的内容。(这个只在NFA的正则引擎里有,DFA的正则引擎是没有的,关于这2个的区别,大家可以问下谷歌哥。)
概念这么没什么好说的,那么我们就来看看可能引起歧义的几个问题:
- 问:1、分组不存在肿么办?
答:分组不存在则表示字符串本身。 例子:/\1/ 匹配字符串"\1" - 问:2、分组后有量词肿么办?
答:分组后有量词,以最后一次匹配为准。 例子:/(\d)+ \1/ 则对于“12345”\1匹配的是5 - 问:3、括号嵌套了肿么办?
答:从左到右匹配。 例子: /(\d(\d(\d))\1\2\3)/ 对于字符串“123”有如下表现: \1:"123" \2:"23" \3:"3" - 问:4、反向引用也被分组包括了肿么办?
答:将匹配出来的结果分组出来给之后使用。 例子:/(\d)(\1)\2/ === /(\d)(\1)\1/ 此处的\2匹配结果和\1是完全一样的 - 问:5、如果不希望前面的括号被匹配该肿么办?
答:使用非捕获括号(?:),作用就是使括号仅仅是括号,不存储,无法被反引用 例子:/(?:\d)(\d) \1/ 对于支付串“12 2”而言,"\1"将匹配第二个字符,也就是2, 非捕获括号能减少存储,加速,是正则优化的措施之一
###上面2个实例分别说明”正则优化代码”以及”不得不用正则”的场景,至于最后一个”装逼利器”则完全是因为大部分人看不懂你写的是什么…对!别人都看不懂了还不够你装逼吗?这真的是在夸正则吗😶
到此为止,本文应该结束了吧。
不不,正文才刚刚开始呢~
js的正则引擎是NFA的,所以还有2个很重要的点要说说
- 一个是贪婪匹配
- 一个是回溯
贪婪匹配
上概念:指的就是尽可能多的匹配,与之对应的就是惰性匹配,js在默认情况下是贪婪匹配
还是看例子吧:
/\d{2,10}/ 尽可能多的匹配,”0123456789”匹配的结果就是”0123456789”
如何变成惰性匹配,很简单,如下:
/\d{2,10}?/ 匹配到结果,就马上停止,”0123456789”匹配的结果就是”01”
* 贪婪匹配很强大很有意思,同时也可能导致很多回溯,影响性能
默认的贪婪匹配只需要在量词({m,n},{m,},*,+,?等)后面加上“?”就会变成惰性匹配
回溯
继续上概念:当正则表达式模式包含可选限定符或替换构造时,会发生回溯,并且正则表达式引擎会返回以前保存的状态,以继续搜索匹配项。
官方概念果然难懂,我们翻译下:就是让字符串从左到右逐个匹配,直到失败,然后又逐个回退,回退到可以成功的位置,再往前进行匹配…。
回溯是正则表达式强大功能的中心;它使得表达式强大、灵活,可以匹配非常复杂的模式,同时他也是导致了正则效率低的罪魁祸首
文字不好说明,还是举个例子吧,假如需要匹配字符串”<div>12”
正则为/<.+>\d{2}/,匹配的结果如下:
如果把正则改成/<\w+>\d{2}/,则匹配结果如下:
看了以上2个图你应该已经明白了什么是回溯。第二个正则是对第一个正则的优化,减少了回溯的次数,提高了正则的效率,这个也是正则优化的主要方向。
那么说到这里也就顺理成章的说说正则的优化了吧!
no,no,no, 这个说来话长,我们还是留待后续吧,敬请期待…PS:文中其实已经提出了几处优化的方向
###结束语 over
* 差点忘记了,如果没考虑好密码强度验证该怎么写的,这里给个例子:/(?=.\d)(?=.[A-Z])(?=.[a-z])^[0-9A-Za-z]+$/