当前位置:首页 > 前端 > 正文内容

三十分钟包会——正则表达式

放牧的风1个月前 (05-24)前端139

p3-juejin.byteimg.com_tos-cn-i-k3u1fbpfcp_0fab67c8c1964377a3a69d98c0ad0bc9_tplv-k3u1fbpfcp-watermark.image.png

一、前言

正则表达式,对大家来说既熟悉又陌生。熟悉是因为工作中有很多场景能用到,比如手机号、邮箱、密码等规则校验。

陌生则是因为正则表达式看上去就是一堆乱码,且一眼看上去很难看懂匹配规则。有时候在网上去找一个特定规则的正则表达式,搜出来的结果各不相同,执行效果更是不尽人意,想自己去修改,感觉也无从下手。

今天就花费30分钟时间,带领大家从另一个角度去剖析匹配的目的,理解匹配的思路,一步一步抽丝剥茧去学会怎么写正则表达式(读正则表达式远比写正则表达式要困难的多)。

二、理解正则要干的事情

正则要干的事情,实际上就是你要干的事情,可以总结为以下灵魂三问: Q1、匹配啥? Q2、匹配不是啥? Q3、匹配多少次?

Q1、匹配啥?

这个比较好理解,比如想匹配字符a,那就直接写/a/,只要字符串某个位置是a就可以匹配上:

/a/.test("javascript") //true 复制代码


匹配以a开头的字符串,就加上元字符^(开始位置标识),/^a/

/^a/.test("javascript") //不是以a开头返回false 
/^a/.test("abc") //是以a开头返回true


匹配以a结尾的字符串,就加上元字符$(结束位置标识),/a$/

/a$/.test("javascript") //不是以a结尾返回false 
/a$/.test("cba") //是以a结尾返回true


匹配字符a或b,可以把对应的字符放入中括号里/[ab]/,只要字符串包含a或者b就可以匹配上:

/[ab]/.test("byte") //true


匹配字符串abc或xyz,/abc|xyz/

/abc|xyz/.test("aabbxyz") //本字符串包含xyz,所以返回true


① 匹配啥前面的(使用前瞻)

前瞻 (?=exp):匹配一个位置,这个位置后面是exp

exp1(?=exp2):匹配exp2前面的exp1,匹配结果不包含exp2

比如要匹配字符串中script前面的部分java, /java(?=script)/

/java(?=script)/.test("javascript,javaee,typescript") //字符串中javascript符合规则 会返回ture 

//1、用exec方法来验证下匹配的结果 
/java(?=script)/.exec("javascript,javaee,typescript") 

//2、得到匹配结果如下: 
["java", index: 0, input: "javascript,javaee,typescript", groups: undefined] 

//3、会发现匹配到的是java,index是0,说明找到了的是javascript中script前面的java


② 匹配啥后面的(使用后顾)

后顾 (?<=exp) : 匹配一个位置,这个位置前面是exp

(?<=exp2)exp1:匹配exp2后面的exp1,匹配结果不包含exp2

比如要匹配字符串中java后面的ee, /(?<=java)ee/

/(?<=java)ee/.test("javascript,javaee,typescript") //字符串中javaee符合规则 会返回ture 

//1、用exec方法来验证下匹配的结果 
/(?<=java)ee/.exec("javascript,javaee,typescript")  

//2、得到匹配结果如下: 
["ee", index: 15, input: "javascript,javaee,typescript", groups: undefined] 

//3、会发现匹配到的是ee,index是15,说明找到了的是javaee中java后面的ee


Q2、匹配不是啥?

匹配不是啥,意思就是取反,只要不是这些的都可以匹配,比如不想匹配字符a,正则写法为/[^a]/,是在中括号里面加上元字符^,只要字符串满足有不是这个集合里面的字符,就可以被匹配上:

/[^a]/.test("aaa") //字符串全是a,返回false 
/[^a]/.test("abc") //字符串不全是a,返回true


匹配不是以a开头的,跟之前匹配啥类似,加上元字符^/^[^a]/

/^[^a]/.test("javascript") //此字符串不是以a开头,返回true 

/^[^a]/.test("abc") //此字符串是以a开头,返回false


匹配不是以a结束的,也跟之前匹配啥类似,加上元字符$/[^a]$/

/[^a]$/.test("javascript") //此字符串不是以a结束,返回true 
/[^a]$/.test("cba") //此字符串是以a结束,返回false


不匹配字符a、b、c,可以把对应的字符都放入中括号里/[^abc]/

/[^abc]$/.test("abccba") //此字符串的字符全部都不符合,返回false


① 匹配后面不是啥的(使用负前瞻)

负前瞻 (?!exp):匹配一个位置,这个位置后面不是exp

exp1(?!exp2):匹配后面不是exp2的exp1,匹配结果不包含exp2

比如要匹配字符串中后面不是script的java, /java(?!script)/

/java(?!script)/.test("javascript,javaee,typescript") //字符串javaee符合规则 会返回ture 

//1、用exec方法来验证下匹配的结果 
/java(?!script)/.exec("javascript,javaee,typescript") 

//2、得到匹配结果如下: 
["java", index: 11, input: "javascript,javaee,typescript", groups: undefined] 

//3、会发现匹配到的是java,index是11,说明找到了的是javaee中的java,因为这个java后面是ee


② 匹配前面不是啥的(使用负后顾)

负后顾 (?<!exp):匹配一个位置,这个位置前面不是exp

(?<!exp2)exp1:匹配前面不是exp2的exp1,匹配结果不包含exp2

比如要匹配字符串中前面不是java的script,/(?<!java)script/

/(?<!java)script/.test("javascript,javaee,typescript") //字符串中typescript符合规则 会返回ture 

//1、用exec方法来验证下匹配的结果 
/(?<!java)script/.exec("javascript,javaee,typescript") 

//2、得到匹配结果如下: 
["script", index: 22, input: "javascript,javaee,typescript", groups: undefined] 

//3、会发现匹配到的是script,index是22,说明找到了的是typescript中type后面的script


③ 匹配不包含连续abc的字符串

这是一个比较特殊的匹配行为,如果只是写成/[^abc]/的话,这个集合内部是的关系。这意味着字符串不能包含abc这三个中的任意一个,不能检测出字符串是否包含连续的abc

那我们要从另外一个角度去分析,字符串的任意一个位置开始都不能连续出现abc,我们可以利用负前瞻的特性来匹配,然后一步一步来实现这个正则:

  1. 匹配位置后面不能是abc,使用负前瞻匹配这样的位置:/(?!abc)/

  2. 这个位置后面可以是其他的字符,用\w来表示:/(?!abc)\w/

  3. 满足上面情况后的位置,可以连续多次,用+来表示数量:/((?!abc)\w)+/

  4. 从开始到结束每个位置都要覆盖到,添加开始结束标记:/^((?!abc)\w)+$/

/^((?!abc)\w)+$/.test("cbacbac") //本字符串中不包含连续的abc,结果返回true 
/^((?!abc)\w)+$/.test("cbacbabc") //本字符串中包含连续的abc,结果返回false


这里大家可能还会有个疑问:为什么用负前瞻(后面不是啥),不用负后顾(前面不是啥),只要校验每个位置前面不是abc就行了?

答案是不行。因为正则是从字符串的第一个位置往后来校验的,任何一个字符串第一个位置的前面都是空,因此第一个字符串就不满足前面不是abc这个条件,所以不行。

不过本案例也完全可以使用前瞻来判断字符串是否包含abc,这里只是去介绍负前瞻怎么使用。

图解前瞻后顾中前和后的含义

p6-juejin.byteimg.com_tos-cn-i-k3u1fbpfcp_020aa22d83e54a1c830b71e9652cdec4_tplv-k3u1fbpfcp-watermark.image.png

图中绿色字符和位置是已经被匹配检测过的,红色是当前正在匹配检测的,灰色是还未被匹配检测的,匹配的方向是从左往右。前瞻和后顾中的前和后指的是匹配方向的前后,不是字符串位置的前后。前瞻和后顾也有地方被叫做正向零宽断言和负向零宽断言(零宽的意思是只匹配位置不匹配字符),这里正向和负向指的也是匹配方向。

所以前瞻就是从正在匹配的位置往匹配方向的前方看,去判断前方的字符是否为exp;后顾就是从正在匹配的位置往匹配的后方看,去判断后方的字符是否为exp;负前瞻则是往前方看,去判断前方的字符是否不为exp;负后顾是往后方看,判断后方的字符是否不为exp。

额外说明:后顾和负后顾在某些语言或者环境下是不支持的,使用时请谨慎验证支持情况

为了更好理解前瞻和后顾等知识点,我又专门写了一篇解析文章,感兴趣的可以看一下《一图搞定正则中的前瞻和后顾

Q3、匹配多少次?

匹配一次可以什么都不用定义,比如匹配一个数字/\d/,如果要匹配连续三个数字最简单的方式就是连续写三次:/\d\d\d/,这样写本身是没有问题的,能正确匹配。

但是如果次数太多或者次数不确定,这么写肯定不行,所以可以加上长度规则:

*:匹配任意次

+:最低匹配1次

?:匹配1次或者0次

{m}:匹配m次

{m,}:最低匹配m次

{m,n}:最低匹配m次,最多匹配n次,m需要小于等于n

正则默认是贪婪匹配,就是符合条件的会一直匹配,如果想阻止贪婪匹配,可以在长度规则后面加一个?,比如:

/\d{2,}/.exec("1234567890") //得到匹配结果如下,会匹配到所有数字: 
["1234567890", index: 0, input: "1234567890", groups: undefined] 

//1、添加?,阻止非贪婪匹配后 
/\d{2,}?/.exec("1234567890") 

//2、得到匹配结果如下,只会匹配2个数字: 
["12", index: 0, input: "1234567890", groups: undefined]


① 使用分组

如果想匹配多次某个单词如regregregregregreg时候怎么办,我们看到reg连续出现了6次,如果傻傻的把6个reg写在了正则表达式中肯定不合适,我们就可以利用分组来实现,我们把reg放在括号里面,然后让这个分组重复6次,/(reg){6}/

/(reg){6}/.test("regregregregregreg") //匹配成功,返回true


不过这样利用分组是有个前提,就是知道要匹配的字符串就是reg,然后重复这个分组。如果想匹配类似8899或者5522这种连续重叠类型但是不确定具体是什么的字符怎么办呢?那我们可以把重叠的第一个放入分组,再通过\n(n表示第几个分组)捕获这个分组内容来匹配下一个:

/(\d)\1(\d)\2/.exec("2345566789") 

//得到匹配结果如下,返回了5566,分组对应的5、6也被返回 
["5566", "5", "6", index: 3, input: "2345566789", groups: undefined]


② 分组捕获

默认的分组是可以被捕获的,上面的\1\2是在正则表达式内部捕获的分组。如果想在外部去捕获分组匹配的数据可以使用RegExp.$1-$9来获取。只要正则匹配了就会有。可以使用testexec或者str的replace方法来获取$1-$9

使用test:

/([a-z]{2})(\d{2})/.test("xyz123") 
RegExp.$1 //返回第一个分组表达式匹配到的内容 yz 
RegExp.$2 //返回第二个分组表达式匹配到的内容 12


使用replace:

"xyz123".replace(/([a-z]{2})(\d{2})/,'$2$1') 
//会返回结果:x12yz3,就是把第一个分组匹配到的内容yz和第二个分组匹配到的内容12互换了


③ 分组不捕获

如果不想捕获分组,只需要在分组内加上?:就可以了

/([a-z]{2})(?:\d{2})/.test("xyz123") 
RegExp.$1 //返回第一个分组表达式匹配到的内容 yz 
RegExp.$2 //分组未被捕获 返回空字符串


三、总结

本文不去赘述元字符的含义以及组合使用方法,这些是需要记死背硬的东西。而是教给大家一种如何切入正则表达式的思维,怎么去一步步分析要匹配的需求,把长的、复杂的需求拆成短的、简单的,正向不行的就逆向去分析,一点一点去组合,然后回归灵魂三问:匹配啥?匹配不是啥?匹配多少次? 去完成符合要求的正则表达式。

最后推荐给大家一个图解正则的网站:regexper.com



原文链接:https://juejin.cn/post/6939854031787393031

扫描二维码推送至手机访问。

版权声明:本文由放牧的风发布,如需转载请注明出处。

本文链接:https://faka.grazingwind.com/post/55.html

分享给朋友:

相关文章

前端性能优化的知识(上)

前言引言反复看下以下三个问题。有木有不同的人问过你:什么是前端性能优化?有木有不同的面试官问过你:你为前端性能优化做过什么?有木有哪一次,你问过自己:别人问我前端性能优化到底应该如何答复?你有木有一套自己的关于性能优化的答案,能让技术大牛和...

如何理解HTTP响应的状态码?

如何理解HTTP响应的状态码?

我们知道HTTP协议是通过HTTP请求和HTTP响应来实现双向通信的。 HTTP状态码(HTTP Status Code)是用以表示Web服务器HTTP响应状态的3位数字代码,由RFC 2616规范定义。 合理的状态码不仅可以让用...

NPM install -save 和 -save-dev 傻傻分不清

回顾 npm install 命令我们在使用 npm install 安装模块的模块的时候 ,一般会使用下面这几种命令形式:npm install moduleName # 安装模块到项目目录下 &...

前端性能优化的知识(下)

引言当遇见“你为性能优化做了哪些事情”,70% 的人上来就说减少合并资源、减少请求、数据缓存这些优化手段;15% 的人会提到需要在 DevTools 下先看看首屏时间,围绕首屏来优化;10%的人会提到需要接入一个性能平台来看看现状,诊断一下...

npm更新模块并同步到package.json中

使用原始npm1.查看需要更新的版本npm outdated该命令会列出所有需要更新的项目2.修改package.json中需要更新的包对应的版本号npm update由于npm update只能按照package.js...

经得住拷问的HTTPS原理解析

经得住拷问的HTTPS原理解析

此文涵盖的大致内容:理解HTTPS原理的概念什么是对称加密和非对称加密?什么是数字签名?怎么生成?怎么校验?啥时候是对称加密?啥时候是非对称加密?啥时候进行算法加密?什么算法?第三方机构包含哪些?HTTPS 是什么?具体流程HTTPS和HT...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。