JavaScript正则表达式

JavaScript正则表达式


正则表达式速查表

推荐:正则表达式图形描述网站 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 的任意字母,都可以使用范围类
下面看一下不使用范围类的情况

  • 匹配 0 - 9 的数字
/[0123456789]/.test("今年66大顺");
// 返回结果:true
  • 匹配 a - z 的字母

… 没法写了,需要从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"

开始和结束

正则表达式中 ^ 作为开始符号、$ 作为结束符号

^:将^后面的 一个字符 做为开始字符
$:将$前面的 一个字符 做为结束字符

具体用法如下:

  • 开始符号
    需求:将第一个 @ 符号替换为 a

不使用^时

"@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的意思,意思是全局匹配,还有两个 im,分别代表着忽略大小写,和多行匹配,他们可以在一起使用

标识符 含义 全称
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个数字连续出现三次,

未完待续。。。。。


  1. \u4e00-\u9fa5 ↩︎

你可能感兴趣的:(JS,正则表达式,javascript)