正则表达式

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中的,第二个参数里用1、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

文章到这里差不多要结束了,文章有点长,感谢各位读者能抽出宝贵时间点进来查看我的文章,文章有什么错误,也欢迎大家指正,再会啦!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容