正则表达式是很强大的工具,常用于字符串匹配与表单验证。以前只是在需要使用的时候百度一个,自己验证一下能满足使用场景就得过且过了,对原理并不了解。为长远着想,还是应该学习一下。
正则表达式的本质是描述一段字符串遵循的规则,其匹配模式主要有两种:匹配字符,匹配位置
通常情况下,匹配字符就能满足大多数场景,而匹配位置则更多地应用于验证
一、字符匹配
1.匹配模式
1.1横向模糊匹配——一个正则可匹配的字符长度不固定,使用量词计数字符出现次数的区间
模板:{m,n}表示最少连续出现m次,最多连续出现n次(特别注意,是连续出现)
let regex = /ab{2,5}c/g;
// 表示 a 和 c 中间,b 最少连续出现2次,最多连续出现5次
let string = 'abc,abbc,abbccc,abbbb,abbbbbc,abbbbbbc';
console.log(string.match(regex));
// ['abbc', 'abbc', 'abbbbbc']
1.2纵向模糊匹配——具体到某一个字符的时候,它可以有多种可能
模板:[abc]表示该字符可以是 a, b, c中的任何一个
let regex = /a[123]b/g;
// 表示 a 和 c 中间的字符可以是 1, 2, 3
let string = 'a0b,a1b,a2b,a3b,a4b,a5b';
console.log(string.match(regex));
// ['a1b', 'a2b', 'a3b']
2.字符组——表示某一个字符所有的可能(只匹配一个字符)
上面的[123]就是一种字符组
2.1范围表示法——如果字符组中的字符特别多(即要匹配的字符有很多可能性),可用范围表示
模板:[123456abcdefGHIJKLM]可以写成[1-6a-fG-M](注意:使用范围表示法的前提是属于范围的字符需要时连续的, 12456就不能写成1-6,因为缺3)
let regex = /a[1-3f-zM-O]2/g;
let string = 'absa22,cag2m,daN2';
console.log(string.match(regex));
// ['a22', 'ag2', 'aN2']
特殊情况:如果需要匹配 '-' ,'a', 'z'中任意一个,要么改顺序[-az]或[az-],要么转义[a\-z],总之不能让引擎识别成范围表示
2.2排除字符组——某位字符可以是任何东西,但不能是'a', 'b', 'c',可以使用取反排除的概念
模板:[^abc],表示匹配的只以为字符可以是除了a,b,c的任一个。^(脱字符)表示求反
let regex = /a[^abc]3/g;
let string = 'bcaa3,ba63c,cda443,daG3M';
console.log(string.match(regex));
// ['a63', 'aG3']
2.3常见的字符组简写形式
\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\u20208\u2029] | 通配符,表示几乎所有字符。换行,回车,行分隔,段分隔除外 |
特殊技巧:匹配任意字符——[\d\D],[\w\W],[\s\S], [^]
3.量词
3.1简写形式
{ m, } | 下限为m,无上限 |
{ m } | m次 |
? | { 0,1 },表示出现或不出现 |
+ | { 1, },下限为1,无上限,即至少出现一次 |
* | { 0, },下限为0,无上限 |
3.2贪婪匹配和惰性匹配
贪婪匹配——会尽可能多的把符合条件里出现次数最多的匹配出来
let regex = /\d{2,5}/g;
// 表示数字连续出现2到5次
let string = '123,1234,12345,123456';
console.log(string.match(regex));
// ['123','1234', '12345', '12345']
连续数字出现2-5次都是符合的,但是因为是贪婪的,会尽可能长的匹配,所以如果5次符合,就匹配5次的。优先级是5次>4次>3次>2次
找12的时候其实已经符合条件了,但是因为是贪婪模式,所以发现123也符合,就继续了,直到,不匹配才重新从出现一次开始匹配
特别注意:匹配过的字符不会再次匹配,所以最后匹配到12345的时候就截断输出了,不会再匹配23456,因为前面的2345已经匹配过了
惰性匹配——再符合条件的匹配结果里尽可能少的匹配
let regex = /\d{2,5}?/g;
// 表示数字连续出现2到5次
let string = '123,1234,12345,123456';
console.log(string.match(regex));
// ["12", "12", "34", "12", "34", "12", "34", "56"]
连续数字出现2-5次都是符合要求的,但是惰性匹配会优先匹配短的,只要出现2次符合就直接截断开始下一个匹配阶段,优先级是2>3>4>5
所以输出都是连续两个的数字
量词后加上?就能实现惰性匹配:{m,n}? {m,}? ?? +? *?
4.多选分支——支持多个子模式任选其一,即符合其中一个模式就够了
模板:(p1 | p2 | p2),用 | (管道符)分隔,表示其中任一
let regex = /good|nice/g;
let string = 'good idea, nice boy';
console.log(string.match(regex));
// ["good", "nice"]
分支模式默认是惰性匹配的,当两个模式是包含关系的时候,前面匹配上了,后面的就不匹配了
let regex = /goodbye|good/g;
let string = 'goodbye';
console.log(string.match(regex));
// ["goodbye"]
let regex = /good|goodbye/g;
let string = 'goodbye';
console.log(string.match(regex));
// ["good"]
5.案例分析
匹配字符的本质无非就是字符组,量词和分支结构组合使用罢了
5.1匹配16进制颜色值
要求匹配:#ffbbad, #Fc01DF, #FFF, #ffE
要注意的是分支结构的顺序
let regex = /#\w{6}|\w{3}/g;
因为要求匹配6位或3位的,,所以量词直接使用{3}和{6},如果需要匹配4,5位的,则应该使用贪婪模式
5.2匹配时间(以24小时制为例)
要求匹配: 23:59 02:07
分析:第一位为2时,第二位为[0-3];第一位为[01]时,第二位为[0-9],第三位为[0-5],第四位为[0-9]
let regex = /^([01]\d|[2][0-3]):[0-5]\d$/;
console.log(regex.test('23:59'));
// true
注意:尾部不带g,不要全匹配;^匹配行开头,$匹配行结尾
如果也要求匹配 7:9 ,也就是时,分的高位为0可省略,正则如下:
let regex = /^(0?\d|1\d|2[0-3]):(0?\d|[1-5]\d)$/;
console.log(regex.test('3:9'));
// true
5.3匹配日期(比如 yyyy-mm-dd格式)
要求匹配: 2017-06-10
分析:年,四位数字表示即可——[0-9]{4}或\d{4}
月,共12个,分两种情况,高位为0和高位为1——(0[1-9]|1[0-2])
日,最大为31——(0[1-9]|[12]\d|3[01])
let regex = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
console.log(regex.test('2017-06-10'));
// true
5.4windows操作系统文件路径
要求匹配: F:\study\javascript\regex\regular expression.pdf
F:\study\javascript\regex\
F:\study\javascript
F:\
分析:整体模式——盘符:\目录\目录\目录\
盘符,不区分大小写,注意:\ 要转义——[a-zA-Z]:\\
目录:不能包含特殊字符,使用取反;不能为空,用量词 + ;‘目录\’可能出现多次——([^\\:*<>|''?\r\n/]+\\)*
最后一部分可以没有\,因此要去掉\\,加上?——([^\\:*<>|"?\r\n/]+)?
5.5匹配标签的id
要求:从`<div id="container" class="main"></div>`字符串中提取id="container"
let regex = /id=".*?"/;
let string = `<div id="container" class="main"></div>`;
console.log(string.match(regex));
. 表示通配符,全匹配
* 量词,表示可多次出现
?惰性匹配,即从id="开始,再次匹配到"的时候结束
二、位置匹配
1.什么是位置?
可以理解为相邻字符之间的空字符。进行位置匹配的时候,尽量吧字符串理解成每个字符之间都有一个空字符
例如:hello = "h" + "" + "l" + "" + "l" + "" + "o";
2.如何匹配位置
需要使用锚字符来匹配:^ $ \b \B (?=p) (?!p)
2.1 ^ 和 $
^(脱字符)匹配整个目标字符串的开头,在多行的情况下则匹配每一行的开头
$(美元符)匹配整个目标字符串的结尾,在多行的情况下则匹配每一行的结尾
例如:把字符串中的开头和结尾替换成#(可以理解为插入,或者把开头和结尾的空字符替换成#)
console.log(`hello`.replace(/^|$/g, `#`));
// #hello#
多行匹配时每一行都有开头和结尾,需要用 gm 修饰符
console.log(`I\nLOVE\nYOU`.replace(/^|$/gm, `#`));
// #I#
// #LOVE#
// #YOU#
注意:管道符 | 会使用惰性模式匹配,如果结尾不加g修饰符,会匹配到开头之后直接停止
2.2 \b 和 \B
\b(单词边界),具体就是\w和\W之间的空字符
例如:字符串 [LOVE], \b匹配到的就是 [ 和 L之间的空字符,以及 E 和 ] 之间的空字符
let regex = /\b/g;
let string = `[regex] Test_1.js`;
console.log(string.replace(regex, `#`));
// [#regex#] #Test_1#.#js#
2.3 (?=p)和(?!p)
(?=p)—— 表示p前面的位置 ex:(?=a)表示a前面的位置
let regex = /(?=l)/g;
console.log('hello'.replace(regex, '#'));
// he#l#lo
(?!p)——表示的是(?=p)的反面意思,即除了p前面的位置以外的所有位置
let regex = /(?!l)/g;
console.log('hello'.replace(regex, '#'));
// #h#ell#o#
二者的学名分别是 positive lookahead(正向先行断言) 和 negetive lookahead(负向先行断言)
ES6中还支持(?<=p)和(?<!p),宜用例子去理解