1、什么是正则表达式
正则表达式是由一个字符序列组成的搜索模式,可用于所有文本的搜索和替换操作。
2、语法
/正则表达式主题/修饰符(可选)
例子:var reg = /sevenSma/i
3、修饰符
i 不区分大小写
g 全局匹配
m 多行匹配
4、语法
4.1、范围表示
比如 [123456abcdEFGH] 可写为 [1-6a-dE-H]
但是因为连字符-有特殊用途,所以要匹配'a' '-' 'z'任意一个字符怎么写呢?
不能写成 [a-z],因为这个代表a到z中的任意一个小写字母,可以写成以下几种方式
[-az] [az-] [a\-z] 将-放在开头或者结尾,或者转义
4.2、排除字符
类似反义字符组的概念,例如 [^xyz],就是除开'x' 'y' 'z' 的任意字符
4.3、元字符
\d就是[0-9],表示一个数字, digit就是数字的英文
\D就是[^0-9],表示非数字
\w就是[0-9a-zA-Z_],表示数字,大小写字母和下划线,w(word),单词的意思
\W就是[^0-9a-zA-Z_],表示非单词
\s就是[ \t\v\n\r\f],表示空白符,包括空格,水平制表符,垂直制表符,换行符,换页符,回车符
\S就是[^ \t\v\n\r\f],表示非空白符
.就是[^\n\r\u2028\u2029],表示通配符,表示几乎任何字符串。换行符,回车符,行分隔符和端分隔符除外
\b,表示单词边界
\B,表示非单词边界
\0,表示 NUL字符
\n,表示换行符
\f,表示换页符
\r,表示回车符
\t,表示制表符
\v,表示垂直制表符
4.4、量词
{m,n}表示出现m次到n次,
{m,}表示至少出现m次
{m}={m,m}表示出现m次
?={0,1}表示没有或者1次
+={1,}表示至少出现1次
*={0,}表示没有或者n次
4.4.1、贪婪匹配和惰性匹配
先看例子
var reg = /\d{2,5}/g
var str = "1 12 123 1234 12345 123456"
console.log(str.match(reg)) //['12', '123', '1234', '12345', '12345']
其中正则/\d{2,5}/g表示数字连续出现2到5次,会匹配2位、3位、4位、5位连续数字
这就是贪婪匹配,它会匹配所有能匹配上的
修改一下上面的例子
var reg = /\d{2,5}?/g
var str = "1 12 123 1234 12345 123456"
console.log(str.match(reg)) //['12', '12', '12', '34', '12', '34', '12', '34', '56']
其中/\d{2,5}?/g表示,虽然2到5次都行,但当2个就够的时候,就不在往下尝试了。
在量词后面加个问号就能实现惰性匹配,因此所有惰性匹配情形如下:
{m,n}?
{m,}?
??
*?
+?
4.5、多选分支
一个模式可以实现横向和纵向模糊匹配,而多选分支可以支持多个子模式任选其一
具体形式如:(p1|p2|p2),其中p1、p2、p3就是子模式,表示任意之一
比如例子
var reg = /good|nice/g
var str = 'good boy, nice girl'
console.log(str.match(reg)) //['good', 'nice']
再比如
var reg = /good|goodbye/g
var str = 'goodbye'
console.log(str.match(reg)) //['good']
再把模式换一下顺序
var reg = /goodbye|good/g
var str = 'goodbye'
console.log(str.match(reg)) //['goodbye']
也就是说,分支匹配也是惰性的,前面匹配上了,后面就不会匹配了
5、位置匹配
5.1、什么是位置?
位置就是相邻字符串之间的位置,比如下图
5.2、锚字符
在es5中,共有6个锚字符
^$\b\B(?=p)(?!p)
5.2.1、^和$
匹配开头和和结尾
比如
var res = 'goodboy'.replace(/^|$/g,'#')
console.log(res) //'#goodboy#'
5.2.2、\b和\B
\b是单词边界,具体就是\w和\W之间的位置,也包括\w和^、\w和$之间的位置。
比如
var res = '[js] lesson_01.mp4'.replace(/\b/g,'#')
console.log(res) // '[#js#] #lesson_01#.#mp4#'
分析:
第一个'#',两边是'['和'j',也就是\W和\w之间的位置
第二个'#',两边是's'和']',也就是\w和\W之间的位置
第三个'#',两边是' '和'l',也就是\W和\w之间的位置
第四个"#",两边是"1"与".",也就是\w和\W之间的位置。
第五个"#",两边是"."与"m",也就是\W和\w之间的位置。
第六个"#",其对应的位置是结尾,但其前面的字符"4"是\w,即\w和$之间的位置。
知道\b之后,\B就很好理解了
var res = '[js] lesson_01.mp4'.replace(/\B/g,'#')
console.log(res) // '#[j#s]# l#e#s#s#o#n#_#0#1.m#p#4'
在字符串的位置里面,除开\b,剩下的就是\B
5.2.3、(?=p)和(?!p)
(?=p),其中p是一个子模式,就是p前面的位置
比如(?=a),就是字符'a'前面的位置
比如
var res = 'javascript'.replace(/(?=a)/g,'#')
console.log(res) //'j#av#ascript'
而(?!p),就是(?=p)反面的意思
比如
var res = 'javascript'.replace(/(?!a)/g,'#')
console.log(res) //'#ja#va#s#c#r#i#p#t#'
二者的学名分别是positive lookahead和negative lookahead。
中文翻译分别是正向先行断言和负向先行断言。
练习:
1、数字的千位分隔符表示法
比如把"12345678",变成"12,345,678"。
把相应的位置替换成','
先弄出最后一个逗号
var res = '12345678'.replace(/(?=\d{3}$)/g,',')
console.log(res) //'12345,678'
然后再弄出所有逗号,因为逗号出现的位置,要求后面3个数字一组,也就是\d{3}至少出现一次。
使用量词+
var res = '12345678'.replace(/(?=(\d{3})+$)/g,',')
console.log(res) //'12,345,678'
但是还有有问题
var res = '123456789'.replace(/(?=(\d{3})+$)/g,',')
console.log(res) //',123,456,789'
上面的正则表示从尾向前,每三个数字前加',',所以就有这样的问题,
解决的办法就是匹配的位置不能是开头,
(?!^)
var res = '123456789'.replace(/(?!^)(?=(\d{3})+$)/g,',')
console.log(res) //'123,456,789'
2、验证密码,密码长度3-6位,有数字和字母组成且必须包含数字和字母两种字符
一步一步来,先写出3-6的数字或字母字符正则
var reg = /^[a-zA-Z0-9]{3,6}$/
再来判断是否包含数字或字母字符,
var numberReg = /(?=.*[0-9])/ //包含数字字符
var letterReg = /(?=.*[a-zA-Z])/ //包含字母(大小写)字符
//合并起来
var reg = /(?=.*[0-9])(?=.*[a-zA-Z])^[a-zA-Z0-9]{3,6}$/
reg.test('123456') //false 只有数字
reg.test('abcDEF') //false 只有字母
reg.test('1abcDEF') //false 超过6位
看到上面三个正则结果的前两个false能想到什么呢?
只有数字或者只有字母都是不能满足条件的,所以我们可以考虑另外一个方向
至少包含数字和小写字母、至少包含数字和大写字母可以理解为
在满足 /^[a-zA-Z0-9]{3,6}$/ 后同时不能全部是数字,或者不能全部是字母
不能全部是什么,怎么表示呢?
铛铛铛!!! (?!p) 突然出现
var notAllNumberReg = /(?!^[0-9]{3,6}$)/ //不能全部是数字
var notAllLetterReg = /(?!^[a-zA-Z]{3,6}$)/ //不能全是字母
//合并起来
var reg = /(?!^[0-9]{3,6}$)(?!^[a-zA-Z]{3,6}$)^[a-zA-Z0-9]{3,6}$/
reg.test('123456') //false 全是数字
reg.test('abcDEF') //false 全是数字
reg.test('123abc') //true
reg.test('123ADC') //true
reg.test('abCD12') //true
5.2.4、(?<=p)和(?<!p)
说到(?=p)和(?!p)就顺便说下(?<=p)和(?<!p)
前面讲的(?=p)是匹配模式p前面的位置
var res = 'javascript'.replace(/(?=a)/g,'#')
console.log(res) //'j#av#ascript'
而(?!p)是(?=p) 反面的意思
var res = 'javascript'.replace(/(?!a)/g,'#')
console.log(res) //'#ja#va#s#c#r#i#p#t#'
(?<=p)是匹配模式p后面的位置
var res = 'javascript'.replace(/(?<=a)/g,'#')
console.log(res) //'ja#va#script'
而(?<!p)则是表示 (?<=p)反面的意思
var res = 'javascript'.replace(/(?<!a)/g,'#')
console.log(res) //'#j#av#as#c#r#i#p#t#'
6、正则表达式括号
正则表达式也是一门语言,括号的存在让这门语言更加强大,对括号的运用也能从一定程度上反映出对正则的掌握程度。
6.1、分组与分支结构
最直觉,最原始的功能
6.1.1、分组
例如正则 /a+/是匹配连续出现的'a',而要匹配连续出现的'ab',则需要 /(ab)+/
var reg = /(ab)+/g
var str = 'ab abb abab ababab'
console.log(str.match(reg)) //['ab', 'ab', 'abab', 'ababab']
6.1.2、分支结构
多选分支结构(p1|p2)
6.2、引用分组
这是括号一个很重要的功能,有了它,我们就可以进行数据提取以及一些更强大的操作。
以日期为例,假定日期格式为 yyyy-mm-dd,我们可以先写一个简单的正则
var reg = /\d{4}-\d{2}-\d{2}/
然后修改成括号版本的
var reg = /(\d{4})-(\d{2})-(\d{2})/
这样做了有什么好处呢?
6.2.1、提取数据
比如提取出年月日
var reg = /(\d{4})-(\d{2})-(\d{2})/
var str = '2012-12-22'
console.log(str.match(reg)) //['2012-12-22', '2012', '12', '22', index: 0, input: '2012-12-22']
match方法的说明:存放匹配结果的数组。该数组的内容依赖于 reg 是否具有全局标志 g。
没有全局标志g:匹配时只会执行一次匹配,如果没有匹配到任何内容,那么match()会返回null。否则match返回的是一个数组,第一个元素是整体匹配的结果,然后是各个分组匹配的内容,除了前几个常规数组元素外,还有对象属性,index是匹配的的第一个字符在文本中的位置,input是进行匹配的文本。
有全局标志g:match则会进行全局检索,找出所有匹配的字符串,如果没有匹配到任何内容,match会返回null。如果有匹配内容,则会返回数据,数组内容是所有的匹配字符串,也没有index和input属性
var reg = /(\d{4})-(\d{2})-(\d{2})/g
var str = '2012-12-22'
console.log(str.match(reg)) //['2012-12-22']
顺便说下正则方法exec
var reg = /(\d{4})-(\d{2})-(\d{2})/
var str = '2012-12-22'
console.log(reg.exec(str)) //['2012-12-22', '2012', '12', '22', index: 0, input: '2012-12-22']
exec方式的说明:存放匹配结果的数组。该数组的内容依赖于 reg 是否具有全局标志 g。
没有全局标志g:和match结果一样
有全局标志g:exec会稍微复杂一点,exec调用时在字符串中匹配的位置是由一个类似内置的lastindex决定的。在第一次调用exec之前,lastindex初始为0,当你第一次调用exec之后,lastindex会被设置为匹配的最后一个内容的下一个位置,当你再次调用exec时,匹配的位置就是从字符串的lastindex开始匹配,当你反复调用exec时,如果exec没有匹配到任何内容,则会返回null,并将lastindex重置为0
例子:
var reg = /(\d{4})-(\d{2})-(\d{2})/g
var str = '2012-12-22 2022-09-11'
console.log(reg.exec(str)) //['2012-12-22', '2012', '12', '22', index: 0, input: '2012-12-22 2022-09-11']
console.log(reg.exec(str)) //['2022-09-11', '2022', '09', '11', index: 12, input: '2012-12-22 2022-09-11']
console.log(reg.exec(str)) //null
console.log(reg.exec(str)) //['2012-12-22', '2012', '12', '22', index: 0, input: '2012-12-22 2022-09-11']
6.2.2、替换
比如,想把yyyy-mm-dd格式,替换成mm/dd/yyyy怎么做?
var reg = /(\d{4})-(\d{2})-(\d{2})/
var str = '2022-09-11'
console.log(str.replace(reg,'$2/$3/$1')) //'09/11/2022'
其中replace中的,第二个参数里用2、$3指代相应的分组。等价于如下的形式:
var reg = /(\d{4})-(\d{2})-(\d{2})/
var str = '2022-09-11'
var res = str.replace(reg,function(){
return RegExp.$2 + '/' + RegExp.$3 + '/' + RegExp.$1
})
console.log(res); //09/11/2022
6.3、反向引用
除了适用相应的api来引用分组,也可以引用正则本身的分组,但只能引用之前出现的分组,就是反向引用
还是以日期为例
需要一个正则支持下列三个格式
2022-09-11
2022.09.11
2022/09/11
按照之前讲的写出来可能是
var reg = /\d{4}(-|\.|\/)\d{2}(-|\.|\/)\d{2}/
console.log(reg.test('2022-09-11')) //true
console.log(reg.test('2022.09.11')) //true
console.log(reg.test('2022/09/11')) //true
console.log(reg.test('2022-09/11')) //true
虽然匹配了要求的情况,但是像'2022-09/11'这样的数据不是我们需要的数据,我们需要前后一致的情况,这个时候就需要反向引用了
var reg = /\d{4}(-|\.|\/)\d{2}\1\d{2}/
console.log(reg.test('2022-09-11')) //true
console.log(reg.test('2022.09.11')) //true
console.log(reg.test('2022/09/11')) //true
console.log(reg.test('2022-09/11')) //false
里面的\1,表示引用前面的(-|.|/) ,不管他匹配到什么,\1都匹配和它同样的字符
\1表示前面的第一个分组,那么\2、\3分别表示第二个第三个分组,那么问题来了,括号嵌套怎么办,比如
var reg = /((\d)(\d(\d)))\1\2\3\4/
var str = '1231231233'
console.log(reg.test(str)) //true
来分析这个正则
1、\1第一个分组((\d)(\d(\d))),匹配123
2、\2第二个分组(\d),匹配1
3、\3第三个分组(\d(\d)),匹配23
4、\4第四个分组(\d),匹配3
此外\10也表示第10个分组
那引用不存在分组怎么样呢?
因为反向引用,是引用之前的数组,但如果我们引用了前面不存在的分组,此时也不会报错,只是会匹配反向引用字符本身,比如\3,就匹配'\3',注意'\3'表示对'3'进行了转义
var reg = /\1\2\3\4\5\6\7\8\9/
var str = '\1\2\3\4\5\6\7\8\9'
console.log(reg.test(str)) //true
7、总结
平时我们平时撸码的时候正则确实带来了很多便利,尤其是在操作字符串的时候,都会下意识的使用正则。但是我们也要认识到正则的局限性,也需要思考是否可以不用正则也能完成对字符的操作。比如我们前面的例子,提取日期里面的年月日。
var reg = /(\d{4})-(\d{2})-(\d{2})/
var str = '2012-12-22'
console.log(str.match(reg)) //['2012-12-22', '2012', '12', '22', index: 0, input: '2012-12-22']
但其实用split也能完成年月日的提取
var str = '2012-12-22'
console.log(str.split('-')) //['2012', '12', '22']
或者是判断字符串是否包含某个字符串
比如判断日期是否是2012年
var str = '2012-12-22'
var reg = /^[2012]/g
console.log(reg.test(str)) //true
其实也可以用字符串的indexOf方法
var str = '2012-12-22'
console.log(!!~str.indexOf('2012')) //true
文章到这里差不多要结束了,文章有点长,感谢各位读者能抽出宝贵时间点进来查看我的文章,文章有什么错误,也欢迎大家指正,再会啦!!!