推荐:正则表达式图形描述网站 https://regexper.com
原义文本字符
原义文本字符就是要匹配的原文本内容,比如匹配 abc ,那么 abc 就是原义文本字符
/abc/.test("abc"); // 返回结果:true
元字符
元字符就是在正则表达式中有特殊含义的非字母字符,比如:\d,它并不是匹配一个反斜线和一个字母d,而是匹配从0-9的数字字符
/\d/.test("22"); // 返回结果:true
在正则表达式中以下字符为特殊字符,是有特殊含义的:
一般情况下正则表达式一个字符对应字符串一个字符,如:
ab\t 匹配的是 ab+tab
\t 是水平制表符,就是在文本编辑中我们按一下tab键
但是有些情况下我们不是想要匹配某个字符,而是想匹配某类字符,就是符合一系列特征的字符
这时候我们可以使用 [] 这个元字符,来构建一个简单的类,所谓的类就是指符合某些特征的对象
使用字符类:
/[abc]/.test("the blue sky good"); // 返回结果:true
上面的正则表达式为什么会返回true?是因为我们使用了字符类,就是字符串中只要包含a、b、c任意一个字母都会匹配成功,就是abc只要有任意一个字母出现就行,而不是直接去匹配 abc
不使用字符类:
/abc/.test("the blue sky good"); // 返回结果:false
/abc/.test("the blue sky a、b、c good"); // 返回结果:false
/abc/.test("the blue sky abc good"); // 返回结果:true
我们看上面的正则表达式,是没有使用字符类
第一个出现了字母b,没有匹配成功
第二个出现了字母abc,但是每个字母没有连续出现,字母之间隔断了,所以也没有匹配成功
第二个出现了字母abc,因为是连续出现,所以匹配成功
再一个例子
"a1a2 b1b2 c1c2".replace(/[abc]/g, "X"); // 将字符串 a、 b、c 的字符替换成字符串 x
// 返回结果:"X1X2 X1X2 X1X2"
上面的正则表达式 /[abc]/g 我们现在只关注 /[abc]/ 即可,后面的 g 先不用管他什么意思
使用元字符 ^ 创建 反向类/负向类,反向类的意思就是不属于某类的内容
注意:^ 还有另一个意思,就是匹配字符串的开始位置
如:匹配不是 a 或b 或 c的内容
"a1a2 b1b2 c1c2".replace(/[^abc]/g, "X"); // 将字符串不是 a、 b、c 的字符替换成字符串 x
// 返回结果:"aXaXXbXbXXcXcX"
上面替换结果可以看到,将非 a 或 b 或 c的字符全部替换成了 x ,连空格都替换了
范围类 是指匹配某一范围的字符,比如匹配 从0 - 9的任意数字、从a - z 的任意字母,都可以使用范围类
下面看一下不使用范围类的情况
/[0123456789]/.test("今年66大顺");
// 返回结果:true
… 没法写了,需要从a到z写26个字母,太麻烦了
使用范围类的情况
/[0-9]/.test("今年66大顺");
// 返回结果:true
/[a-z]/.test("今天刚学了abc英文单词");
// 返回结果:true
使用范围类将 a - z 的字母全部替换成 X
"a1a2 b1b2 c1c2 A3 B3 C3".replace(/[a-z]/g, "X");
// 返回结果:"X1X2 X1X2 X1X2 A3 B3 C3"
仔细看上面的替换结果,我们只是将小写的字母给替换了,大写的字母没有替换掉,如何将大写和小写都替换?
其实字符类中可以连写的
使用范围类将 a - z 和 A-Z 的字母全部替换成 X
"a1a2 b1b2 c1c2 A3 B3 C3".replace(/[a-zA-Z]/g, "X");
// 返回结果:"X1X2 X1X2 X1X2 X3 X3 X3"
我们看上面的一个例子,a - z 和 A - Z 都正确匹配了,但是我要是有下面的一个需求呢
需求: 将 2020-05-01 替换为 AAAAAAAAAA
我们试一下:
"2020-05-01".replace(/[0-9]/g, "A");
// 返回结果:"AAAA-AA-AA"
发现 0 - 9 任意数字都确实替换了,但是 - 却没有替换,因为 - 如果出现在范围类中字符开头到字符结尾中间的话,则为认为是范围的意思,那我们就不在字符开始和结尾中间添加 - 了,我们在后面加
"2020-05-01".replace(/[0-9-]/g, "A");
// 返回结果:"AAAAAAAAAA"
这样就成功替换了
正则表达式提供了一些预定义类,目的是让我们的书写更方便
预定义类列表(加粗的代表常用的):
字符 | 含义 | 等价类 |
---|---|---|
. | 匹配除回车符和换行符外的所有字符 | [^\r\n] |
\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er” | |
\B | 匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er” | |
\cx | 匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符 | |
\d | 匹配一个数字字符 | [0-9] |
\D | 匹配一个非数字字符 | [^0-9] |
\f | 匹配一个换页符 | \x0c和\cL |
\n | 匹配一个换行符 | \x0a和\cJ |
\r | 匹配一个回车符 | \x0d和\cM |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等 | [ \f\n\r\t\v] |
\t | 匹配一个制表符 | \x09和\cI |
\v | 匹配一个垂直制表符 | \x0b和\cK |
\w | 匹配包括下划线的任何单词字符 | [A-Za-z0-9_] |
\W | 匹配任何非单词字符 | [^A-Za-z0-9_] |
\xn | 匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41”匹配“A”。“\x041”则等价于“\x04&1” | |
\num | 匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符。 | |
\n | 标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值 | |
\nm | 标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\nm将匹配八进制转义值nm | |
\nml | 如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml | |
\un | 匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©) | |
/\p{Unified_Ideograph}/u | 匹配汉字,这里使用了Unicode属性类,并不是所有浏览器都支持 | |
/1+$/ | 匹配汉字,要匹配非汉字,只需要在中括号里面开始位置添加 ^ 即可 |
比如有一个需求,匹配ab开头后面为数字再后面有除回车换行任意字符,我们以前可能这样写
/ab[0-9][^\r\n]/.test("ab6A?");
// 返回结果:true
有了预定义类则更方面一些,可以这样写
/ab\d./.test("ab6A?");
// 返回结果:true
将 is 字符替换为 66
不使用预定义类
"this is a boy".replace(/is/g, "66")
// 返回结果:"th66 66 a boy"
上面结果我们发现是有问题的,将 this 单词中 is 也给替换了,我们可以使用单词边界 \b 来解决
使用预定义类
"this is a boy".replace(/\bis/g, "66") // 正则表达式中,在 is 前面添加 \b
// 返回结果:"this 66 a boy"
正则表达式中 ^ 作为开始符号、$ 作为结束符号
^:将^
后面的 一个字符 做为开始字符
$:将$
前面的 一个字符 做为结束字符
具体用法如下:
不使用^时
"@123@456@789@".replace(/@/g, "a");
// 返回结果:"a123a456a789a"
上面我们看到并不是我们想要的效果,我们只是要替换第一个@,这里将全部@都替换了
现在我们使用**开始符号^来标记只替换以@**开头的字符
使用^时
"@123@456@789@".replace(/^@/g, "a"); // 在 @ 前面添加 ^
// 返回结果:"a123@456@789@"
上面的结果就是我们想要的效果,标识替换以 @ 开始的,后面的 @ 因为前面有其他字符,所以不会匹配到
那我们如果想替换以**@结尾的呢?可以使用结束符号$**
"@123@456@789@".replace(/@$/g, "a"); // 在 @ 后面添加 $
// 返回结果:"@123@456@789a"
上面就已将最后一个@给替换了,标识匹配以@结尾的字符
在正则表达式中有三个标识符,最常用的就是 g,是 global的意思,意思是全局匹配,还有两个 i 和 m,分别代表着忽略大小写,和多行匹配,他们可以在一起使用
标识符 | 含义 | 全称 |
---|---|---|
g | 全局匹配 | global match |
i | 忽略大小写 | ignore case |
m | 多行匹配 | multiple |
我们分别介绍三个标识符的使用
全局匹配
现在有这么一个需求,要将 2020-05-11 替换为 20200511
我们直接替换试试
"2020-05-11".replace(/-/,'~'); // 将字符串中 - 替换为 ~
// 返回结果:"2020~05-11"
我们发现,实际上只是替换了第一个 - ,后面的就不会再替换了,我们使用的文本编辑器中的替换也是这样的
现在我们添加 g 标识符,代表着全局替换,也就是文本编辑器中的全部替换功能
"2020-05-11".replace(/-/g,'~'); // 将字符串中 - 替换为 ~
// 返回结果:"2020~05~11"
我们一直都是使用 // ,其实 // 就会返回正则表达式对象 RegExp,就像 “” 会返回 String对象一样
RegExp对象使用注意事项
如果我们不使用//,或者说正则表达式规则在一个字符串变量中,我们这时候可以直接使用RegExp对象
"2020-05-11".replace(new RegExp("-", "g"),'~'); // 将字符串中 - 替换为 ~
// 返回结果:"2020~05~11"
上面是使用了RegExp对象,第一个参数为正则表达式,第二个参数为标识符,参数都是String类型
RegExp使用需要有些注意的地方,那就是牵扯到 \ 的时候,因为 \ 对String内来说是转移符
看下面例子
/abc\d/g.test("abc123"); // 正则表达式匹配 前面是abc字母,后面是数字
// 返回结果:true
上面使用 // 返回的RegExp匹配字符串abc123,这时候我们发现匹配是没问题的,返回true,我们再试试使用 RegExp 对象是怎么样
new RegExp("abc\d", "g").test("abc123"); // 正则表达式匹配 前面是abc字母,后面是数字
// 返回结果:false
我们仔细看会发现,一模一样的正则表达式,使用 RegExp 就不行,这是为什么呢?主要问题出在了 \d ,因为在 RegExp 对象中,我们是字符串写的正则表达式,通过构造函数传递过去的,在字符串里面 \ 是个转移符,想要正常使用,需要写两个 \
new RegExp("abc\\d", "g").test("abc123"); // 正则表达式匹配 前面是abc字母,后面是数字
// 返回结果:true
忽略大小写
我们再来看一下 i 标识符的使用,现在有这么一个需求,将 “a” 和 “A” 替换为 “诶”
const reg = "这货纯,典型的a货,而且还是大写的A";
reg.replace(/a/g,'诶'); // 将字母a替换为汉字诶
// 返回结果:"这货纯,典型的诶货,而且还是大写的A"
我们看上面,我们明明使用了g,理应应该是全局匹配的,但是现在只匹配了前面的a,后面的A没有替换
原因就是默认区分了大小写,我们现在添加 i 不区分大小写试试
const reg = "这货纯,典型的a货,而且还是大写的A";
reg.replace(/a/gi,'诶'); // 将字母a替换为汉字诶
// 返回结果:"这货纯,典型的诶货,而且还是大写的诶"
我们添加了 i 标识符后,发现不管是 a 还是 A 都替换了
多行匹配
前面两个还好理解一些,这个多行匹配又是什么玩意?
我们现在打开Chrome调试工具,在控制台声明一个变量,为:let mul = "@123\n@456\n@789"
我们观察上面的变量,内容是:@123 换行 @456 换行 @789 换行
变量声明后,在浏览器控制台打印一下 mul 变量,发现变量的值如下:
> let mul = "@123\n@456\n@789"
> mul
<· "@123
@456
@789"
我们现在有这么一个需求:
将上面的字符串 @和后面1位数字 替换为 X,我们先分析上面的字符串,看正则表达式应该怎么写
通过上面的字符串,我们发现三行数字都是以 @ 开始,我们想替换@和后面的1位数字,也想到应该这样写
/^@\d/g
上面的正则表达式刚开始一个 ^@
代表匹配以@开始,后面一个 \d
,代表匹配后面一位数字,再添加g全局匹配,我们试一下看看效果
let mul = "@123\n@456\n@789";
mul.replace(/^@\d/g, "X");
/*
返回结果:
"X23
@456
@789"
*/
我们发现这不是我们想要的,我们明明要替换的是 以@开头+后面1位数字,替换为X,并且是全局的,这三行都符合要求,为什么只有第一个被替换了?
原因是我们看到的是三行,其实对JavaScript来说换行符也是个字符,就是 \n,我们认为是三行,并且都是@开头,JavaScript认为是一行,只有第一个是@开头,后面的@因为前面有字符所以就不是开头
那如何让正则表达式认为另外两个 @ 是新的一行呢? 那就是加上 m标识符,多行匹配
let mul = "@123\n@456\n@789";
mul.replace(/^@\d/gm, "X");
/*
返回结果:
"X23
X56
X89"
*/
添加m后,发现正则表达式将新行的@也认为是新的开始,表示将换行符也认为是新的一行
什么是量词?其实就是指定数量,就是一个字符出现了多少次
量词是标注前面的 一个字符 出现的次数
比如我们有这样一个需求:
匹配一个前面以abc开头,后面连续出现10次数字结尾的正则表达式,我们可能会这样写
/^abc[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/g.test("abc3287652886");
// 返回结果:true
上面这样写可能有点长,10个还好点,如果要求50个呢?可能我们自己都得数数
还记得我们前面说过的 预定义类 吗?可以用预定义类来实现
/^abc\d\d\d\d\d\d\d\d\d\d$/g.test("abc3287652886");
// 返回结果:true
好像用预定义类也有点长,要求50个我们还得一个个数,这时候就可以用上量词了
下面是正则表达式量词列表
字符 | 含义 |
---|---|
? | 出现0次或1次(最多出现1次) |
+ | 出现1次或多次(至少出现1次) |
* | 出现0次或多次(任意次) |
{n} | 出现n次 |
{n,m} | 出现n到m次 |
{n,} | 至少出现n次 |
上面的量词我们一个个看
? 的使用
最多出现1次
需求:要求限制产品编号中间可以出现#号,但是最多只能有一个#,就是前面1个数字,中间可以出现#,后面一个数字
/\d#\d/.test("3#2"); // 不使用? 匹配3#2,返回结果:true
/\d#\d/.test("32"); // 不使用? 匹配32 返回结果:false
/\d#?\d/.test("3#2"); // 使用? 返回结果:true
/\d#?\d/.test("32"); // 使用? 返回结果:true
执行结果:
第一个因为我们没有使用 ?,匹配为:前面一个数字,中间一个#,后面一个数字,我们的内容符合条件,返回true
第二个我们也没有使用 ?,匹配为:前面一个数字,中间一个#,后面一个数字,我们的内容中间没有 # ,没有匹配成功
第三个我们使用了 ?,匹配为:前面一个数字,中间最多出现一个#,后面一个数字,我们的内容有 #,匹配成功
第四个我们使用了 ?,匹配为:前面一个数字,中间最多出现一个#,后面一个数字,我们的内容没有 #,匹配成功
+ 的使用
至少出现1次
/\d#\d/.test("3#2"); // 不使用+ 匹配3#2,返回结果:true
/\d#\d/.test("32"); // 不使用+ 匹配32 返回结果:false
/\d#+\d/.test("3#2"); // 使用+ 返回结果:true
/\d#+\d/.test("3##2"); // 使用+ 返回结果:true
/\d#+\d/.test("32"); // 使用+ 返回结果:false
执行结果:
第一个没有使用 +,内容符合条件
第二个没有使用 +,内容不符合条件
第三个使用了 +,内容符合条件
第四个使用了 +,内容符合条件
第四个使用了 +,内容不符合条件
*** 的使用**
出现0次或多次(任意次)
/\d#\d/.test("3#2"); // 不使用* 匹配3#2,返回结果:true
/\d#\d/.test("32"); // 不使用* 匹配32 返回结果:false
/\d#*\d/.test("3#2"); // 使用* 返回结果:true
/\d#*\d/.test("3##2"); // 使用* 返回结果:true
/\d#*\d/.test("32"); // 使用* 返回结果:true
执行结果:
因为 * 表示出现0次,或任意次,所以只要加上后,全部符合条件
{n} 的使用
出现n次
{n}代表指定出现的次数,n是个数字,{n}可以指定前面的一个字符出现的次数
现在有这么一个场景,要求以g开头,中间必须有10个o,以gle结尾
/^go{10}gle$/.test("gooogle"); // o不到10个 返回结果:false
/^go{10}gle$/.test("goooooooooogle"); // o到10个 返回结果:true
/^go{10}gle$/.test("gooooooooooogle"); // o超过10个 返回结果:false
执行结果:
第一个o出现次数不到10个,条件不符合
第二个o出现次数正好10个,条件符合
第三个o出现次数超过10个,条件不符合
{n,m} 的使用
出现n到m次
场景:要求以g开头,中间必须出现3-5次o,以gle结尾
/^go{3,5}gle$/.test("google"); // o不到3个 返回结果:false
/^go{3,5}gle$/.test("goooooooooogle"); // o超过5个 返回结果:false
/^go{3,5}gle$/.test("gooogle"); // o正好3个 返回结果:true
/^go{3,5}gle$/.test("goooogle"); // o出现4个 返回结果:true
/^go{3,5}gle$/.test("gooooogle"); // o出现5个 返回结果:true
{n,} 的使用
至少出现n次
场景:要求以g开头,中间至少出现3次o,以gle结尾
/^go{3,}gle$/.test("google"); // o不到3个 返回结果:false
/^go{3,}gle$/.test("goooooooooogle"); // o超过3个 返回结果:true
/^go{3,}gle$/.test("gooogle"); // o正好3个 返回结果:true
以上就是量词的使用,现在我们使用量词看一下刚开始的那个需求
需求:匹配一个前面以abc开头,后面连续出现10次数字结尾的正则表达式,我们可能会这样写
不使用量词:
/^abc\d\d\d\d\d\d\d\d\d\d$/g.test("abc3287652886");
// 返回结果:true
使用量词:
/^abc\d{10}$/g.test("abc3287652886");
// 返回结果:true
现在我们有这么一个场景,匹配数字3-6次
正则表达式: \d{3,6}
字符串: 12345678
上面的情况我们会发现,无论是匹配3次还是4次还是5次还是6次都会匹配成功,那么这时候正则表达式会这么处理呢?他会按3次匹配还是按6次匹配,还是按中间的4次或5次?
我们看一下示例
"12345678".replace(/\d{3,6}/, "X"); // 将字符串中数字前3个或前6个替换为X
// 返回结果:"X78"
我们看到返回结果是 “X78”,也就是说正则表达式默认是尽可能的按照最多的去匹配,匹配了6次,而不是匹配3次
这就是正则表达式的 贪婪模式,JavaScript中正则表达式默认就是贪婪模式,就是直接匹配到头,匹配指定的最大次
JavaScript中正则表达式默认就是贪婪模式,那怎么使用非贪婪模式呢?那就是 在量词后面使用? 即可
还是使用上面的案例:将字符串中数字前3个或前6个替换为X
"12345678".replace(/\d{3,6}/, "X"); // 贪婪模式,会匹配指定最大次数,返回结果:"X78"
"12345678".replace(/\d{3,6}?/, "X"); // 非贪婪模式,会匹配指定最小次数,返回结果:"X45678"
如果使用 g 会是这样的效果
// 贪婪模式,匹配最大次数,将123456替换为X
"12345678".replace(/\d{3,6}/g, "X"); // 返回结果:"X78"
// 非贪婪模式,会匹配指定最小次数,发现123符合条件,则将123替换为X,发现456符合条件,会将456也替换为X
"12345678".replace(/\d{3,6}?/g, "X"); // 返回结果:"XX78"
我们上面学到了量词,看似非常万能,现在我们有这样一个场景
场景:匹配 safety 这个单词连续出现3次
我们马上就想到正在表达式应该这样写:/safety{3}/
代码如下:
// 匹配 safety 单词连续出现3次
/safety{3}/g.test("safetysafetysafety"); // 返回结果:false
// 将前面3个 safety 单词替换为A
"safetysafetysafety".replace(/safety{3}/g, "A"); // 返回结果:safetysafetysafety
通过上面代码我们看到,返回结果好像不是那回事,一个没有匹配成功,一个压根就没替换,字符串是符号条件的
其实原因是量词只是标注前面 一个字符 连续出现3次,也就是 safety 单词最后一个字母连续3次,而不是指的整个单词
那怎么让量词认为 safety 这个单词是一个字符?或者说让量词认为紧挨着它的是 safety 这个单词,而不是 y
这里我们可以进行分组,使用 ()
小括号来进行分组
也就是说正确的正则表达式应该这样写:(safety){3}
// 匹配 safety 单词连续出现3次
/(safety){3}/g.test("safetysafetysafety"); // 返回结果:true
// 将前面3个 safety 单词替换为A
"safetysafetysafety".replace(/(safety){3}/g, "A"); // 返回结果:A
场景2:将 一个小写英文字母 + 一个数字 替换为 X
不使用分组
"a1b2c3".replace(/[a-z]\d{3}/g, "X"); // 返回结果:a1b2c3
上面代码我们可以看到,要将前三个 1个小写字母+1个数字 替换为 X,结果却没有替换
原因是 {3} 这个两次认为 是它自己前面的 \d 也就是数字连续出现3次,而不是1个小写字母+1个数字连续出现三次,
未完待续。。。。。
\u4e00-\u9fa5 ↩︎