前端如何理解正则-由浅入深的学习
引言
正则在平时工作中用得蛮多的,比如说验证、文本搜索、文本替换、服务配置…。之前就常有同事直接发我规则,让我写个正则给他。自己也因为在编辑一个公众号的内容,需要将图片上的文本录入图文(文章)。于是就想着调用百度的图片识别API,将返回的数据格式化 (通过正则判断需要获取的值) 后,再插入网页版公众号编辑器,所以对于正则用得更多了。 所以我觉得正则这东西,只需要掌握其中几个核心的元字符,然后简单练习一下,再找几个稍复杂的案例详细解释,这样就能掌握书写正则的规律,最后再学习缩写后的常用符号就能完全理解了。
正则简单语法
除了普通字符,还有一些元字符则具有特殊的含义,比如下面的这些:
元字符 描述
\ 正则的转义符,有三种情况:
- \ 加上元字符,表示匹配元字符所使用的普通字符,比如要匹配普通字符 \,就要写\。
- \ 加上非元字符,组成一种由具体实现方式规定其意义的元字符序列 如\d表示匹配一个数字字符。
- \ 加上任意其他字符,默认情况就是匹配此字符,也就是说,反斜线被忽略了。
^ 匹配文本行首。如果设置了RegExp对象的Multiline属性, ^也匹配\n或\r之后的位置。用到[]元字符中第一位时是取反的意思。
例如:/^abc/ 匹配 abc 开头的字符串。
/^abc/m 匹配多行 abc 开头的字符串。
$ 匹配文本行尾。如果设置了RegExp对象的Multiline属性, ^也匹配\n或\r之后的位置。
例如:/abc / 匹 配 a b c 结 尾 的 字 符 串 。 / a b c / 匹配 abc 结尾的字符串。 /abc /匹配abc结尾的字符串。 /abc/m 匹配多行 abc 结尾的字符串。
| 逻辑 或 的意思。例如:/a|b/ 匹配 a 或者 b 。
() 将( 和 ) 之间的表达式定义为“组”(group),并且将匹配这个表达式的字符保存到一个临时区域(一个正则表达式中最多可以保存9个),它们可以用\1 到 \9 的符号来引用。
例如:/([a-z])\1/,假如第一个括号内的[a-z]匹配到字母 d,那么\1就相当于d。
以此类推,\2就是第二个括号内匹配到的内容。(后面深入部分举例讲)
[] 带有 或 关系的一组数据,并可定义区间。
例如:[abc]匹配a或b或c。
[a-z]匹配a到z的小写字母。
[^a-z]匹配除a到z之间字符以外的任意单字符,包括空字符。
{} 包含一个(段)数量的量词,给匹配符添加数量,不能为负整数。
例如:/a{2}/,匹配连续的2个a。
/a{2,}/,匹配连续的>=2个a。
/a{0,5}/,匹配连续的>=0 && <=5个a。
当然元字符并不止这么一点,还有更多。 但是只要知道以上几种元字符,就能书写大部分正则规则了,以下用例子把上面描述的内容实际展示一下。
正则语法练习
获取字符串内内的数据(使用字符串方法 match)
var str = ‘今天学习了[RegExp]对象’;
var reg = /[[a-zA-Z]{0,}]/;
console.log( str.match(reg) );
// => ["[RegExp]"]
复制代码
这里就用到了[a-zA-Z],里面规则是匹配大小写字母,而紧跟着的{0,},是匹配0个或多个大小写字母。 前后的[ ],就是用到了\元字符的第一种情况。 - 在[a-z]、[0-9]等等之间属于连字符,表示之间的意思 [a-z-]中z后面的-表示匹配普通字符-,实在不清楚就用\转义 [a-z]匹配所有小写字母 [a-z]匹配a,- ,z。
判断字符串是否存在英文以外的字符(使用正则方法 test )
var str = ‘StackOverflow’;
var str2 = ‘我在TrendyTech上班’;
var reg = /[^a-zA-Z]$/;
console.log( reg.test(str) ); // => false
console.log( reg.test(str2) ); // => true
复制代码
这里在[]内用到了^,意思就是取反中括号内的匹配项,整体的意思就是匹配除大小写字母以外的任意字符。
判断字符串是否为xxxx-xx-xx格式的日期(使用正则方法 test )
var str = ‘2020-01-12’;
var str2 = ‘2020年1月1日’;
var reg = /^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[01])$/;
console.log( reg.test(str) ); // => true
console.log( reg.test(str2) ); // => false
复制代码
这一段正则看起来长,其实拆分一下很简单,总共分为三部分 第一部分[0-9]{4} 匹配年份,年份为四个数字组成 第二部分(0[1-9]|1[0-2]) 匹配月份,0[1-9] 匹配 01~09,1[0-2] 匹配 10~12。 第三部分(0[1-9]|[1-2][0-9]|3[01]) 匹配日期,0[1-9] 匹配 0~9,[1-2][0-9] 匹配 10~29,3[01] 匹配 30,31
虽然此正则不是很严谨,比如小月和平月没有31天,不过能说明规则就好。
从字符串中获取日期(使用字符串方法 match )
var str = ‘今天是2020-01-12,马上就放假了’;
var reg = /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[01])/;
console.log( str.match(reg)[0] ); // => 2020-01-12
复制代码
这次的正则对比上面的只移除了^ $,使用match方法,获取到了字符串中的 xxxx-xx-xx 格式的时间字符串。
常用正则分析
好了,以上几个例子已经能够把正则基础的信息完整讲明了,那我们再解析几个常用的正则,最终你会发现,其实看起来很复杂的正则也是一个一个短的逻辑段拼凑而成。
IP 正则的验证与或获取
大多数情况,验证与获取的区别在于是否添加了行首^、行尾$验证。
var ipReg = /^(((2(5[0-5]|[0-4][0-9]))|[0-1]{0,1}[0-9]{1,2}).){3}((2(5[0-5]|[0-4][0-9]))|[0-1]{0,1}[0-9]{1,2})$/;
复制代码
IP是由 xxx.xxx.xxx.xxx 格式组成,xxx 的值为 0255,所以我们第一步写个0255 的正则。
0~255 的正则 就是 (2(5[0-5]|[0-4][0-9]))|[0-1]{0,1}[0-9]{1,2}
太长了我们在拆分一下,分为 0199,200255 0~199 的正则是 [0-1]{0,1}[0-9]{1,2} 解释:百位是0-1,匹配0-1次就是可以没有百位。个位十位取值0-9,匹配1-2次就是0-99之间的数。 200~255 的正则是 2(5[0-5]|[0-4][0-9]) 解释:百位固定为2,十位这里分为5和0-4,5的情况下个位为0-5,0-4的情况下,个位是0-9。
因为0~199 和 200~255 拼接起来就要用()+或|连接起来,就成了上面0~255的正则。
0~255由.拼接,就成了 0255.0255.0255.0255。 这里由于.是匹配除换行和回车符以外的任意单字符的元字符,所以加斜线.转义为普通字符.。
因为上面 有重复规律就是 0~255. 出现三次所以用()括起来,再用量词{3}乘以三。 0255.0255.0~255. 的正则就是 ((2(5[0-5]|[0-4][0-9]))|[0-1]{0,1}[0-9]{1,2}).){3}
加上最后的0~255就是完整匹配IP的正则了。 用一张图表明:
在这里插入图片描述
正则很长,其实可以稍微减短一点,之前说过,元字符\加非元字符,会有一些常用匹配的集合,比如: [0-9] 可以用 \d替换,{0,1}可以用?替换。 简写一下上面的规则就是
var ipReg = /^(((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2}).){3}((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})$/;
复制代码
像这样的常用匹配集合有很多,在未熟练掌握正则之前先不要使用,可以把写完的正则再一一对应替换。
去掉首尾的^ $,用来匹配字符串中的IP。
var ipReg = /(((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2}).){3}((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})/;
var str = ‘这个项目部署在192.168.101.255上面。’
console.log( str.match(ipReg)[0] ); // => 192.168.101.255
复制代码
邮箱 正则的验证与获取
var emailReg = /1[a-zA-Z0-9_.-]{1,}@([a-zA-Z0-9_-]{1,}.){1,}[a-zA-Z0-9_-]{1,}$/;
复制代码
普通邮箱格式:邮箱名称由 字母、数字、.、、-组成,首字母为字母或数字 域名部分由 字母、数字、、-组成,.连接
邮箱名称正则 [a-zA-Z0-9][a-zA-Z0-9_.-]{1,} 解释:字母、数字开头,后面跟着字母、数字、_、.、-,重复1次或多次
中间加 @连接
邮箱域名正则 ([a-zA-Z0-9_-]{1,}.){1,}[a-zA-Z0-9_-]{1,} 拆分为 [a-zA-Z0-9_-]{1,} 和 .,然后组合成xxx.xxx.xxx格式的邮箱域名正则。 解释:字母、数字、-、_,重复1次或多次
用一张图表示:
在这里插入图片描述
同样邮箱域名也可以缩写,元字符 + 和 {1,} 等价,\w 类似 [a-zA-Z0-9_] (这里是类似,不是等价)。
缩写后的正则就是
var emailReg = /2[\w.-]+@([\w-]+.){1,}[\w-]+$/;
复制代码
还有很多邮箱的规则这里并不完全匹配,如果要匹配比较特殊的邮箱,比如有中文,可以根据以上所学到的自行添加。
去掉首尾的 ^ $ ,用来匹配字符串中的邮箱。
var emailReg = /[a-zA-Z0-9][\w.-]+@([\w-]+.){1,}[\w-]+/;
var str = ‘我的google邮箱是zhouyu0229@gmail.com,你的邮箱呢?。’
console.log( str.match(emailReg)[0] ); // => zhouyu0229@gmail.com
复制代码
正则深入学习
匹配ASCII码与Unicode码表数据
比如说匹配 @ ,我们不单可以用普通字符@,也可以使用 ASCII码的八进制、十六进制匹配和Unicode码匹配,看下面例子。
var str = ‘我是@符号’;
var OCTReg = /\100/; // 八进制ASCII码
var sexadecimalReg = /\x40/; // 十六进制ASCII码
var unicodeReg = /\u0040/; // Unicode码
console.log( str.match(OCTReg)[0] ); // => @
console.log( str.match(sexadecimalReg)[0] ); // => @
console.log( str.match(unicodeReg)[0] ); // => @
复制代码
可以看到,都能匹配到 @ 符号,不但能单个匹配也能区间匹配,比如匹配A 到 D
var str = ‘我是在AB幼儿园上学,小明在CD幼儿园上学,小刚在EG幼儿园上学’;
var OCTReg = /[\101-\104]/g; // 八进制ASCII码
var sexadecimalReg = /[\x41-\x44]/g; // 十六进制ASCII码
var unicodeReg = /[\u0041-\u0044]/g; // Unicode码
console.log( str.match(OCTReg) ); // => [“A”, “B”, “C”, “D”]
console.log( str.match(sexadecimalReg) ); // => [“A”, “B”, “C”, “D”]
console.log( str.match(unicodeReg) ); // => [“A”, “B”, “C”, “D”]
复制代码
可以看到把字符串内的 A B C D,都取出来了,最后的 g ,是修饰符。(下面解释) 所以可以用过区间方式匹配两个码表的所有字符,比如用unicdoe匹配中文字符,中文字符的编码范围是4E00-9FA5,正则就写成[\u4E00-=u9FA5],另外还有很多。完整的ASCII和Unicode码表参考。
修饰符g m i
g 是 global全局匹配,默认情况下是非全局匹配,匹配到一个就结束,全局匹配是匹配所有数据。还有两个修饰符分别是 i m i 是 ignoreCase 忽略大小写的意思很好理解 m 是 multiline 多行匹配,比如 /^a/ 只匹配第一行开头是否为 a ,而加了m ,就是每一行开头都匹配。比如下面:
var ignoreCaseStr = ‘[a-z]和[A-Z]是不同的’;
var multilineStr = a同学大了b同学,b同学不满a同学打了他,就还手打了 a同学
;
var ignoreCaseReg = /[A-Z]/gi;
var multilineReg = /^a/gm;
console.log( multilineStr.replace(multilineReg,‘A’) );
// => A同学大了b同学,b同学不满a同学打了他,就还手打了
// => A同学
console.log( ignoreCaseStr.replace(ignoreCaseReg, ‘x’) );
// => [x-x]和[x-x]是不同的
复制代码
可以看到行首的 a 替换成了 A,而中间的没有,第二个正则内只写了大写的字母匹配,加了 i 修饰符,小写的也被匹配到用replace方法替换成了x。
()组的用法
元字符语法中说道,将( 和 ) 之间的表达式定义为“组”(group),并且将匹配这个表达式的字符保存到一个临时区域(一个正则表达式中最多可以保存9个),它们可以用 \1 到 \9 的符号来引用。特殊用法除外。比如:
var groupReg = /([A-Z])([A-Z])\2/g;
var groupStr = ‘我们公司有很多ABB格式名字的同事,ABC、AB格式的不多,我们吃饭一般都是AA制’;
console.log( groupStr.match(groupReg) ); // => [“ABB”]
复制代码
上面输出了包含 ABB 的数组,正则的意思就是第一个() 内的匹配到A ,如果后面要引用 就用\1 。但我们这里的例子用的 \2,就是用的第二个() 内匹配到的数据,也就是B,所以\2 内临时存的就是 B,因此这里只能匹配第二、第三个字母相同的数据。
用上面写过的获取日期格式的正则来描述一下()组的用法。
var str = ‘今天是2020-01-12,天气很好。’;
var reg = /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[01])/;
console.log( str.replace(reg, $1年$2月$3日
) ); // => 今天是2020年01月12日,天气很好。
复制代码
这里可以看到2020-01-12 替换成了 2020年01月12日,因为上面有三个()组,分别\1 存了年、\2存月、\3存 日,然后使用replace。这里在替换中的用法就是$ + number,和在正则中使用+ number,是一样的,都是一一对应的,并且也最多支持临时存9个。
零宽断言 正则的预查
断言用来声明一个应该为真的事实。正则表达式中只有当断言为 真 时才会继续进行匹配。 零宽断言分为以下四种: (?=pattern) 零宽度 正预测先行 断言(也叫正向肯定预查) 举例:查找15岁的小伙伴
var str = ‘小明17岁,小刚15岁,小红16岁,小茗15岁’;
var reg = /[\u4E00-\u9FA5]{2}(?=15岁)/g;
console.log( str.match(reg) ); // => [“小刚”, “小茗”]
复制代码
以上正则匹配 15岁 之前的两个中文字(不包含断言内的数据),所以输出了 小刚,小茗
(?<=pattern) 零宽度 正回顾后发 断言(也叫反向肯定预查) 举例:查找小茗多少岁
var str = ‘小明17岁,小刚15岁,小红16岁,小茗15岁’;
var reg = /(?<=小茗)\d{2}/g;
console.log( str.match(reg) ); // => [“15”]
复制代码
以上正则查找小茗后面的 两个数字(不包含断言内的数据),所以输出了 15 (?<=pattern) 和 (?=pattern) 同时使用就可以查某某区间的值,比如:
var str = ‘
var reg = /(?<=<(div)>). (?=</\1>)/;
var reg2 = /(?<=<(div)>).?(?=</\1>)/;
console.log( str.match(reg)[0] ); // => 我是div里的内容
console.log( str.match(reg2)[0] ); // => 我是div里的内容
复制代码
这里就输出了 div 标签里的内容,但是我们看到了两种情况。 第一种输出最前端和最后端div之间的数据,第二种是只输出了前面div内的数据。 这也涉及到贪婪模式(*,+,?,{n},{n,},{n,m}默认是贪婪模式),这些限制符后加上?,就是非贪婪模式,就像上方的例子一样,中间的 . 元字符 一个是尽可能的多匹配,一个是尽可能的少匹配。
我们在这里也使用了前面学到的,第一个() (零宽断言的括号不存数据)把取到的 div 暂存,在后面用 \1 取了出来,相当于</div>,/ 需要转义所以使用了 /。
(?!pattern) 零宽度 负预测先行 断言(也叫正向否定预查) 举例:查找不是 15岁 的小伙伴
var str = ‘小明17岁,小刚15岁,小红16岁,小茗15岁’;
var reg = /[\u4E00-\u9FA5]{2}(?!15岁)/g;
console.log( str.match(reg) ); // => [“小明”, “小红”]
复制代码
这个正则就查找了不是 15岁 结尾的前两个字(不包含断言内的数据),输出了不是15岁的 小明、小红
(?<!pattern) 零宽度 负回顾后发 断言(也叫反向否定预查) 举例:查找 小红以外 的小伙伴年龄
var str = ‘小明17岁,小刚15岁,小红16岁,小茗15岁’;
var reg = /(?<!小红)\d{2}/g;
console.log( str.match(reg) ); // => [“17”, “15”, “15”]
复制代码
这个正则就查找了 小红以外 后面跟着两个数字的数据(不包含断言内的数据),输出了 小红以外 其他小伙伴年龄。
零宽断言之密码复杂度
零宽断言不但能匹配数据,同样也能判断数据,比如设置判断密码复杂度的正则: 规则:密码必须包含 字母、数字、_,6~32位。
var reg = /(?=.[a-zA-Z])(?=.\d)(?=.)^\w{6,32}KaTeX parse error: Undefined control sequence: \d at position 50: …*[a-zA-Z])(?=.*\̲d̲)(?=.*_) 判断字符串是… ,判断字符必须是字母、数字、 开头和结尾。 至于为什么前面要写 .,用两个连续的零宽断言测试就能知道了。 (?=[a-zA-Z]) 能判断字符串内是否出现字母 (?=[a-zA-Z])(?=\d) 无法判断字母或数字是否出现,因为判断存在冲突。
在这里插入图片描述
从这张gif动图不难发现,当要匹配的数据是字母开头跟着数字时,断言数字的正则前方必须写已断言匹配到的a,反过来毅然。
因为我们不能控制用户先输入字母、数字、_ 中的哪一个,所以我们在 零宽断言 上加 .* 匹配0个或多个.,是为了不管哪个类型的字符先输入,或者间隔多少字符再输入其它类型字符时,判断其它类型的零宽断言也能继续判断下去(再讲一遍 . 是匹配除换行和回车符以外任意单字符的)。
简单讲,不管单个零宽断言还是多个零宽断言,都是断言的字符串位置,多个断言组合起来判断字符串中是否出现这个组合规则的位置,出现就返回true,不出现就返回fasle。
以上内容不易理解就多读几遍,或者根据gif内容自行测试几遍。
再写一个密码验证 规则:密码必须包含 字母、数字、_中的至少两种6~32位。
var reg = /(?=.[a-zA-Z\d])(?=.[\d_])(?=.*[a-zA-Z_])^\w{6,32}$/;
复制代码
在这里插入图片描述
这里是三种类型字符取两种,这里的 零宽断言 判断的就是所有两两组合的情况,以此类推,四种类型取三种或者两种也是可以的,只是组合情况太多正则就比较长,就建议通过代码分开判断。
结语
正则使用的地方有很多,用熟悉了能极大的提高工作效率,比如多数编辑器的搜索替换都是支持正则的,如果要替换或者搜索某个规律的字段,用正则无疑是最方便快捷的方式了。
以上对正则的介绍就这些,看完这篇信息后,可以去百度百科上看更加详细的文档介绍,不过要注意,上面也有疏漏的地方,毕竟谁都可以改百度百科的内容。
以上内容如有疏漏,或错误的地方欢迎指正,谢谢。
转载 于 https://juejin.im/post/6844904114061574151