Skip to content

Latest commit

 

History

History
470 lines (311 loc) · 12 KB

正则表达.md

File metadata and controls

470 lines (311 loc) · 12 KB

正则表达

正则表达式是匹配模式,要么匹配字符,要么匹配位置!

接下来就从这两方面入手

字符匹配

模糊匹配

横向模糊匹配

横向模糊是指:可匹配的字符串长度不固定。 实现方式:使用量词。 举个栗子, {m,n}表示连续出现最少m次,最多n次

/ab{2,5}c/ 表示匹配“a(2-5个b)c”这样的字符串

let StringTest="ac abc abbc abbbc abbbbc abbbbbc abbbbbbbc"
//g是指全局匹配,在目标字符串中按顺序找到满足匹配模式的所有子串
console.log(StringTest.match(/ab{2,5}c/g))

//控制台输出
 ["abbc", "abbbc", "abbbbc", "abbbbbc"]

纵向模糊匹配

纵向模糊是指:具体到某一位字符时,是不确定的。 实现方式:使用字符组 举个栗子, [abc]表示这个字符可以是字符a、b、c中任意一个

/a[123]b/表示匹配a1b a2b a3b这三种字符串

let StringTest="a0b a1b a2b a3b a4b ab";
console.log(StringTest.match(/a[123]b/g))

//控制台输出
["a1b", "a2b", "a3b"]

字符组

虽然叫字符组,但只是其中一个字符。比如[abc]其实匹配的还是一个字符,只是有3种可能性罢了。

范围表示法

当字符组里字符超多时,可以用范围表示法。 举个栗子,比如[123456abcdefGHIJKLM],可以写成[1-6a-fG-M]

用连字符“-”来省略简化即可

因为连字符有特殊含义,如果需要匹配“a”"-""f"三个字符的话,可以用转义[a\-f]

排除字符组

纵向匹配中,还有一种情况,就是说某位字符可以是任何东西,但不能是a、b、c这种字符。此时可以用脱字符“^”,表示求反。

举个栗子,比如[^abc],表示的是一个除了a、b、c的一个字符。

常用简写

\d [0-9]表示是一位数字 \D [^0-9] 表示是除了数字外的任意字符 \w [0-9a-zA-Z_] 表示数字、大小写字母和下划线 \W [^0-9a-zA-Z_]非单词字符 \s [ \t\v\n\r\f]空白符,包括空格、水平制表、垂直制表、换行符、换页符 \S [^ \t\v\n\r\f]非空白符 .[^\n\r\u2028\u2029]通配符,表示几乎任意字符

量词

简写形式

{m,}至少出现m次 {m}等价于{m,m},出现m次 ?等价于{0,1} 表示出现或者不出现都可 + 等价于{1,}表示至少出现一次 * 等价于{0,}表示出现任意次

贪心匹配和惰性匹配

直接举个栗子吧

let StringTest="123 1234 12345 123456"
console.log(StringTest.match( /\d{2,5}/g))

//控制台输出
["123", "1234", "12345", "12345"]

正则/\d{2,5}/,表示数字连续出现2到5次。会匹配2位、3位、4位、5位连续数字。它会尽可能多的匹配,只要在能力范围内,越多越好。

let StringTest="123 1234 12345 123456"
console.log(StringTest.match( /\d{2,5}?/g))

//控制台输出
["12", "12", "34", "12", "34", "12", "34", "56"]

其中/\d{2,5}?/表示,虽然2到5次都行,当2个就够的时候,就不在往下尝试了。

一般都通过在量词后面+问号实现惰性匹配。

多选分支

一个模式可以实现横向和纵向模糊匹配,多选分支可以支持多个子模式任选其一。

具体形式如下:(p1|p2|p3),其中p1p2p3是子模式,用|(管道符)分隔,表示其中任何之一。

请注意,多选分支是惰性匹配!!

举个栗子

let StringTest="goodbye!"
console.log(StringTest.match( /goodbye|good/g))
console.log(StringTest.match( /good|goodbye/g))

//控制台输出
["goodbye"]
["good"]

当前面的匹配上了,后面的就不再尝试了。

综合使用

匹配16进制颜色

表示一个16进制字符,可以用字符组,其中字符可以出现3或者6次,需要用量词和分支结构(需注意顺序)

let StringTest="#ffbbad #Fc01DF #FFF #ffE #hhhhh";
console.log(StringTest.match(/#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g))

//控制台输出
 ["#ffbbad", "#Fc01DF", "#FFF", "#ffE"]
//但是这有个bug  比如说#ffff会被匹配出来#fff

匹配时间

24小时制

一共是4位数组,第一位可以是0-2。 当第一位是2时,第二位可以是0-3;其他情况是0-9。 第三位数字是0-5,第四位是0-9

let reg=new RegExp(/^([01][0-9]|[2][0-3]):[0-5][0-9]$/)
console.log(reg.test("23:59"));
console.log(reg.test("02:07"));
console.log(reg.test("10:30"));

//控制台输出
true
true
true

匹配日期

比如yyyy-mm-dd的格式

年用4位数字,可以用[0-9]{4}; 月分01 02 ……或者 10 11 12,可以用 (0[1-9]|1[0-2]) 日最大31天,可以用(0[1-9]|[12][0-9]|3[01])

let reg=new RegExp(/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/)
console.log(reg.test("2019-07-22"));

文件路径

整体模式是 盘符:\文件夹\文件夹\文件夹\

匹配盘符用[a-zA-Z]:\\

文件夹文件名不能有特殊字符,使用排除字符组[^\\:*<>|"?\r\n/]表示合法字符,至少有一个字符。所以可以写成[^\\:*<>|"?\r\n/]+\\

文件夹可以出现多次([^\\:*<>|"?\r\n/]+\\)*

最后一个可以是文件夹没有“\”,可有可无用“ ? ”,所以写成([^\\:*<>|"?\r\n/]+)?

let reg=new RegExp(/^[a-zA-Z]:\\([^\\:*<>|"?\r\n/]+\\)*([^\\:*<>|"?\r\n/]+)?$/)
console.log(reg.test("F:\\study\\javascript\\regex\\regular expression.pdf"));
console.log(reg.test("F:\\study\\javascript\\regex\\"));
console.log(reg.test("F:\\study\\javascript"));
console.log(reg.test("F:\\"));

//控制台输出
true
true
true
true

匹配ID

从HTML元素中提取id

最初想法: 任意字符任意次

let StringTest='<div id="container" class="main"></div>'
console.log(StringTest.match(/id=".*"/))
//控制台输出
id="container" class="main"

因为.是通配符,本身就匹配双引号的,而量词*又是贪婪的,当遇到container后面双引号时,不会停下来,会继续匹配,直到遇到最后一个双引号为止。

可以用惰性匹配

let StringTest='<div id="container" class="main"></div>'
console.log(StringTest.match(/id=".*?"/))

//控制台输出
id="container"

但效率比较低,可以修改如下

let StringTest='<div id="container" class="main"></div>'
console.log(StringTest.match(/id=[^"]*"/))

//控制台输出
id="container"

位置匹配

如何位置匹配

^和$

^(脱字符)匹配开头,在多行匹配中匹配行开头。 $(美元符号)匹配结尾,在多行匹配中匹配行结尾。

比如把字符串的开头和结尾用"#"替换

let result="hello".replace(/^|$/g,'#')
console.log(result)

//控制台输出
#hello#

多行匹配模式时,二者是行的概念。

console.log("I\nlove\njavascript".replace(/^|$/gm, '#'))

//控制台输出
#I#
#love#
#javascript#

\b和\B

\b是单词边界,\B非单词边界

比如一个文件名是"[JS] Lesson_01.mp4"中的\b,如下:

console.log("[JS] Lesson_01.mp4".replace(/\b/g, '#'))
console.log("[JS] Lesson_01.mp4".replace(/\B/g, '#'))

//控制台输出
  [#JS#] #Lesson_01#.#mp4#
 #[J#S]# L#e#s#s#o#n#_#0#1.m#p#4

(?=p)和(?!p)

学名叫positive lookahead和negative lookahead。中文翻译分别是正向先行断言和负向先行断言。

(?=p),其中p是一个子模式,即p前面的位置。而(?!p)就是(?=p)的反面意思。 比如(?=l)`,表示'l'字符前面的位置

console.log("hello".replace(/(?=l)/g, '#'))
console.log("hello".replace(/(?!l)/g, '#'))

//控制台输出
he#l#lo
#h#ell#o#

ES6还支持positive lookbehind和negative lookbehind,即(?<=p)(?<!p)`

console.log("hello".replace(/(?<=l)/g, '#'))
console.log("hello".replace(/(?<!l)/g, '#'))

//控制台输出
hel#l#o
#h#e#llo#

关于位置的理解

或许可以把位置理解成空字符""

比如hello 其实可以理解为 ""+""+"hello"

所以/^hello$/是可以写成/^^hello$$$/的,甚至还可以更复杂/(?=he)^^he(?=\w)llo$\b\b$/

综合使用

不匹配任何东西

/.^/即可,要求有一个字符,但该字符后面是开头,等于硕士都不匹配

千分位

先把问题分解一下,首先实现最后一个逗号

(?=\d{3}$) 从末尾找三个数前面的位置

console.log("12345678".replace(/(?=\d{3}$)/g,','))

//控制台输出
12345,678

剩余逗号的实现,每3个数字一组,即(\d){3}至少要出现一次才行。可以用量词+,至少出现一次。

console.log("123456789".replace(/(?=(\d{3})+$)/g,','))

//控制台输出
,123,456,789

新的问题出现了,如果是3的倍数,前面会多一位。

需要要求这个位置不是开头!(?!^)表示即可,其中?!p表示在p之后,^表示开头

console.log("123456789".replace(/(?!^)(?=(\d{3})+$)/g,','))

//控制台输出
123,456,789

接下来完成其他形式的支持,比如说“12345678 123456789”这样的字符串。

需要把正则表达式的开头结尾,替换成\b (单词边界)

console.log("12345678 123456789").replace(/(?!\b)(?=(\d{3})+\b)/g,',')

//控制台输出
12,345,678 123,456,789

(?!\b)表示要求当前是一个位置,但不是\b前面的位置,其实等同于 \B

这样,最终版本的正则就出炉了

console.log("12345678 123456789").replace(/\B(?=(\d{3})+\b)/g,',')

验证密码

密码长度6-12位,由数字、小写字符、大写字符组成,但要求至少包括两种字符。

这个应该是平时代码中常用的了,那么要如何实现呢?

首先,分解问题,先实现密码长度6-12位,由数字、小写字符、大写字符组成

let reg=new RegExp(/^[0-9a-zA-Z]{6-12}$/)

接着实现 至少包括两种字符

首先判断是否包含有一种字符

假定要求必须包含数字,那可以使用(?=.*[0-9])

let reg=new RegExp(/(?=.*[0-9])^[0-9a-zA-Z]{6-12}$/)

同时包含数字和小写字母 可以使用(?=.*[0-9])(?=.*[a-z])

let reg=new RegExp(/(?=.*[0-9])(?=.*[a-z])^[0-9a-zA-Z]{6-12}$/)

那么至少包含两种字符就包括了 1 包含数字小写 2 包含了数字大写 3 包含了小写大写 4 都包含

let reg=new RegExp(/((?=.*[0-9])(?=.*[a-z])|(?=.*[0-9])(?=.*[A-Z])|(?=.*[a-z])(?=.*[A-Z]))^[0-9a-zA-Z]{6-12}$/)

//测试一下
console.log(reg.test("1234567"));//false 全是数字
console.log(reg.test("abcdef")); //false 全是小写字母
console.log(reg.test("ABCDEFGH"));//false 全是大写字母
console.log(reg.test("ab23C")); //false 不足6位
console.log(reg.test("ABCDEF234")); //true 大写字母和数字
console.log(reg.test("abcdEF234")); //true 三者都有

//控制台输出
false
false
false
false
true
true

有没有觉得这种写法似乎有点不太优雅呢?

我们换个思路想,”至少包含两种“是不是就是说 不能全部是数字,也不能全部是小写,也不能全部是大写!

那么只需要(?!p)就可以解决了,不能全是数字的表示是(?!^[0-9]{6-12}$)

let reg=new RegExp(/(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9A-Za-z]{6,12}$/)

//测试
console.log(reg.test("1234567"));//false 全是数字
console.log(reg.test("abcdef")); //false 全是小写字母
console.log(reg.test("ABCDEFGH"));//false 全是大写字母
console.log(reg.test("ab23C")); //false 不足6位
console.log(reg.test("ABCDEF234")); //true 大写字母和数字
console.log(reg.test("abcdEF234")); //true 三者都有

//控制台输出
false
false
false
false
true
true

小结

关于正则的基础简而言之就是这样了。

实际上并不止这些,如果真正弄懂它,还需要弄懂它的匹配原理,比如说回溯。

不过了解了以上这些,在平时的写算法题和一般的基本操作中已经够用了。

更多内容可以查看这篇文章