正则表达式是匹配模式,要么匹配字符,要么匹配位置!
接下来就从这两方面入手
横向模糊是指:可匹配的字符串长度不固定。 实现方式:使用量词。 举个栗子, {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)
,其中p1
、p2
和p3
是子模式,用|
(管道符)分隔,表示其中任何之一。
请注意,多选分支是惰性匹配!!
举个栗子
let StringTest="goodbye!"
console.log(StringTest.match( /goodbye|good/g))
console.log(StringTest.match( /good|goodbye/g))
//控制台输出
["goodbye"]
["good"]
当前面的匹配上了,后面的就不再尝试了。
表示一个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
从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非单词边界
比如一个文件名是"[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
学名叫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
关于正则的基础简而言之就是这样了。
实际上并不止这些,如果真正弄懂它,还需要弄懂它的匹配原理,比如说回溯。
不过了解了以上这些,在平时的写算法题和一般的基本操作中已经够用了。