logo

javascript正则表达式不完全指南

正则表达式(Regular Expression)是用来匹配特定的字符串模式,通过它我们可以对字符串格式进行校验,或者捕获匹配该模式的字符串。其异常强大的字符串处理能力 ,随之而来的 就是有限抽象。很多语言都实现了正则部分以增强对字符串的处理能力,因此掌握正则表达式可以说是编程基本功之一。不同语言之间的正则语法可能会略微有些不同,接下来主要介绍 javascript 中的正则表达式。
推荐一个网站:可视化正则表达式

语法

特殊表达字符与转义

正则有一系列特殊字符来描述字符串的模式关系,特殊字符的组合拼接给予正则表达式异常强大的表达能力。
特殊字符主要有 14 个如下:
^$()[]{}*+.?\|
如果我们需要匹配特殊字符则需要使用 \ 来进行转义,需要特别注意的是:

  • /,斜杠在正则表达式中没有特殊意义,但是当我们使用字面量定义正则表达式时,两个 / 之间是正则表达式,因此需要转义需要匹配的 / 来避免歧义与语法错误
1
2
3
4
5
6
"/".match(/\//);
//["/", index: 0, input: "/", groups: undefined]
"\\".match(/\\/);
//["\", index: 0, input: "\", groups: undefined]
"^$".match(/\^\$/);
//["^$", index: 0, input: "^$", groups: undefined]

字符类别

正则有一系列表达式来表示字符的类别。

模式说明
.小数点用于匹配任意单个字符,行结束字符除外 \n \r \u2028 \u2029.在字符集中([abc.])匹配 .
\d匹配数字等同于[0-9]
\D匹配非数字等同于[^0-9]
\w匹配数字字母下划线等同于[A-Za-z0-9_]
\W匹配非数字字母下划线等同于[^a-za-z0-9_]
\s匹配空白符(空格,制表符,换页符,换行符,一些 unicode 字符等)等价于[ \f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]
\S匹配非空白字符
\b匹配单词边界,一个字符的左 右都存在一个”字符边界”,其并没有具体的值,而单词的定义是有 [a-zA-Z0-9+]组成的字符串,而单词之间就是就存在”单词边界”。 我们用 😁 来表示边界,则表达式 example:a+b=3:字符边界为 😁e😁x😁a😁m😁p😁l😁e😁:😁a😁+😁b😁=😁3😁,单词边界为:😁example😁:😁a😁+😁b😁=😁3😁
\B匹配非单词边界
\t匹配一个水平制表符(tap)
\r匹配一个回车符(Enter)
\n匹配一个换行符(linefeed)
\v匹配一个垂直制表符
\f匹配一个换页符
[\b]匹配一个退格符(backspace)
\0匹配 null 字符\u0000
\xhh匹配编码为 hh 的字符(256 个)
\uhhhh皮配编码为 hhhh 的字符
\可以用于转义特殊字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"\n\r\u2028\u2029".match(/./);
//null
"\u2007".match(/\u2007/);
//[" ", index: 0, input: " ", groups: undefined]
"1".match("\x31");
//["1", index: 0, input: "1", groups: undefined]
"\u0000".match(/\0/);
//["", index: 0, input: "", groups: undefined]
"\b".match(/[\b]/);
//匹配退格符
//["", index: 0, input: "", groups: undefined]
"怎么回事,little borther".replace(/\b/g, "😄");
//"怎么回事,😄little😄 😄borther😄"
"怎么回事,little borther".replace(/\B/g, "😄");
//"😄怎😄么😄回😄事😄,l😄i😄t😄t😄l😄e b😄o😄r😄t😄h😄e😄r"
"怎么回事,little borther".replace(/\B|\b/g, "😄");
//"😄怎😄么😄回😄事😄,😄l😄i😄t😄t😄l😄e😄 😄b😄o😄r😄t😄h😄e😄r😄"

数量词

模式说明
x*匹配模式 x 0 次及以上
x+匹配模式 x 1 次及以上
x?匹配模式 x 0 次或 1 次
x|y匹配模式 x 或者 y
x{n}匹配模式 x 连续出现 n 次 “ruarua”.match(/(rua){3}/),结果为 null
“ruarua”.match(/(rua){2}/),结果为[“ruarua”,”rua”](包括匹配结果和捕获项)
“ruarua”.match(/(?:rua){2}/),结果为[“ruarua”](不包括捕获项)
“ruarua”.match(/(rua){1}/g),结果为[“rua”,”rua”](当存在 g 标识时,不返回捕获项)
“ruarua”.match(/(rua){1}/),结果为[“rua”,”rua”](当不存在 g 标识时,返回捕获项)
“ruarua”.match(/(?:rua){1}/),结果为[“rua”](不包括捕获项)
x{n,}匹配模式 x 至少出现 n 次
x{n,m}匹配模式 x 至少出现 n 次,至多出现 m 次
x+?
x*?
x??
x{n}?
x{n,}?
x{n,m}?
匹配模式 x,取最小匹配(非贪婪模式) ‘aaaaa’.match(/a+?/),结果为[“a”],而不是[“aaaaa”]
1
2
3
4
/4?/.test("abc");
/4+/.test("abc");
/4*/.test("abc");
//true false true
1
2
3
4
5
6
7
8
9
10
"abcabc".match(/c/g);
//["c","c"]
"abcabc".match(/c(?!a)/g);
//["c"]
"abcabc".match(/c(?=a)/g);
//["c"]
"abc".match(/d|c/);
//["c", index: 2, input: "abc", groups: undefined]
"a".match(/a{2,}/);
//null

贪婪模式与非贪婪模式

贪婪模式与非贪婪模式的切换通过 ? 进行切换,默认为贪婪模式(最大匹配)。

1
2
3
4
"aaa".match(/a{2,}?/);
//["c", index: 2, input: "abc", groups: undefined]
"aaa".match(/a{2,}/);
//["aaa", index: 0, input: "aaa", groups: undefined]

字符集合(字符组)

字符集合表示一个字符可匹配值。可以使用 - 来表示连个字符之间的范围,范围为数字字母而且必须从小到大,大小排列顺序为 0-9 ,A-Z ,a-z
因此 [5-x] 等同于 [5-9A-Za-x]

模式说明
[abc]字符集匹配其中任何一个字符
[^abc]反义字符集匹配除此之外的任何字符
1
2
3
4
"regex".match(/[^abc]/);
// ["r", index: 0, input: "regex", groups: undefined]
"459AZaxz".match(/[5-x]+/);
//["59AZax", index: 1, input: "459AZaxz", groups: undefined]

捕获组与非捕获组

使用圆括号可以将一段字符串划分为一组,后面可以接数量词,并且可以被捕获存储。

1
2
"abcabcabc".match(/(abc){2}/);
//["abcabc", "abc", index: 0, input: "abcabcabc", groups: undefined]

可以使用 \数字 的格式来反向引用对应的圆括号内的捕获项。捕获项排序由外向内,再由左往右依次排序。
引用并不是重复捕获项模式,而是重复捕获项已经匹配的字符串模式,如(\d{2})\1(\d{2})\d{2}是完全不相同的两个正则表达式。

1
2
3
4
5
6
7
"abcabbc".match(/(a(b))(c)(\1)(\2)(\3)/);
//["abcabbc", "ab", "b", "c", "ab", "b", "c", index: 0, input: "abcabbc", groups: undefined]
"11322".match(/(\d{2})3\1/);
//null
"11311".match(/(\d{2})3\1/);
//\d{2}匹配到‘11’时,正则等同于(\d{2})311
//["11311", "11", index: 0, input: "11311", groups: undefined]

非捕获组是在捕获项内前添加?:,可以取消返回该捕获项与该捕获项的引用。

1
2
3
4
5
6
7
8
9
"123123".match(/(123)/);
//["123", "123", index: 0, input: "123123", groups: undefined]
"123123".match(/(?:123)/);
//["123", index: 0, input: "123123", groups: undefined]
"123123".match(/(123)(\1)/);
//["123123", "123", "123", index: 0, input: "123123", groups: undefined]
"123123".match(/(?:123)(\1)/);
//(\1)无法获取到捕获项引用
//["123", "", index: 0, input: "123123", groups: undefined]

ES2018 中,可以给捕获项命名在捕获项内最前方添加?<name>命名捕获项,使用\k<key>反向引用捕获项。而且会返回 groups 对象,其中包含正则名于匹配值的键值对。

1
2
3
4
"aa".match(/(?<getA>a)\k<getA>/);
//["aa", "a", index: 0, input: "aa", groups: {…}]
"aa".match(/(?<getA>a)\k<getA>/).groups;
//{getA: "a"}

分支

正则表达式中使用 | 表示分支,|左右两边被分为两个部分,匹配其中一个模式,从左往右。

1
2
3
4
"b".match(/a|b|c/);
//["b", index: 0, input: "b", groups: undefined]
"c".match(/a|b|c/);
//["c", index: 0, input: "c", groups: undefined]

可以使用捕获项来缩小分支作用范围。

1
2
3
4
"ac".match(/a(b|c)/);
//["ac", "c", index: 0, input: "ac", groups: undefined]
"abcde".match(/((a|b)|(c|d))e/);
//["de", "d", undefined, "d", index: 3, input: "abcde", groups: undefined]

开头与结尾

^$元字符分别指定字符串的模式的开头与结尾的位置,这两个符号必须出现在第一或者最后一个符号否则无效,匹配这两个字符需要转义。

1
2
3
4
5
6
"abbb".match(/b/);
//["b", index: 1, input: "abbb", groups: undefined]
"abbb".match(/^b/);
//null
"^a".match(/^\^a$/);
//["^a", index: 0, input: "^a", groups: undefined]

零宽断言

零宽(zero-width)是什么意思?指的就是它匹配一个位置,本身没有宽度。
断言(assertion)是什么意思?指的是一种判断,断言之前或之后应该有什么或应该没有什么。
零宽断言是一种高级的表达方式,可以为指定匹配模式前后的额外匹配模式,只有符合额外的匹配额外模式,该匹配模式才会被匹配。

模式说明
x(?=y)零宽肯定先行断言 ,匹配模式 x 且仅当后面紧跟着 y 时,才匹配 x。
x(?!y)零宽否定先行断言 ,匹配模式 x 且仅当后面不紧跟着 y 时,才匹配 x。
(?<=y)x零宽肯定后行断言(部分浏览器实现) ,匹配模式 x 且仅当前面是 y,才匹配 x。
(?<!y)x零宽否定后行断言(部分浏览器实现) ,匹配模式 x 且仅当前面不是 y,才匹配 x。
1
2
3
4
5
6
7
8
"abac".match(/a(?=c)/);
//["a", index: 2, input: "abac", groups: undefined]
"abac".match(/a(?!c)/);
//["a", index: 0, input: "abac", groups: undefined]
"baca".match(/(?<=c)a/);
//["a", index: 3, input: "baca", groups: undefined]
"baca".match(/(?<!c)a/);
//["a", index: 1, input: "baca", groups: undefined]

修饰符(flags)

  • g:global match,全局匹配
  • i:ignore case,忽略大小写
1
2
3
4
5
6
"abcAbcabc".match(/a/);
//["a", index: 0, input: "abcAbcabc", groups: undefined]
"abcAbcabc".match(/a/g);
//["a", "a"]
"abcAbcabc".match(/a/gi);
//["a", "A", "a"]
  • m:multiline,多行匹配,匹配每一行的开头结尾,而不是整个字符串的开通结尾。(影响$^元字符的行为)
1
2
3
4
5
6
"1\n2".match(/^\d$/);
//null
"1\n2".match(/^\d$/m);
//["1", index: 0, input: "1↵2", groups: undefined]
"1\n2".match(/^\d$/gm);
//["1", "2"]
  • u(ES2015):unicode,处理码点大于 0xFFFF 的字符,有些字符会用多个\uxxx 来表示一个码点,在这种情况下正则表达式往往无法正确处理,不符合处理逻辑,u修饰符可以让正则正确的处理过大的码点。
1
2
3
4
5
6
7
8
9
10
11
var s = '𠮷'; //由两位unicode码组合标识
/^.$/.test(s)
// false
/^.$/u.test(s)
// true
/\D{2}/.test(s)
//被错误识别为两位
//true
/\D{2}/u.test('𠮷')
//正确识别
//false
  • y(ES2015):sticky, 粘滞匹配,根据正则表达式 lastIndex(下次进行匹配的索引位置) 属性指定的位置开始匹配,适用于可以指定匹配位置的字符串模式。
    lastIndex 通常只有在全局匹配中会更新修改,因为其需要从上次匹配的位置继续匹配。
    但是不同的是,只有当粘滞匹配失败时 lastIndex 会被重置为 0,匹配成功的情况下粘滞匹配的 lastIndex 不会更新,必须手动更新 lastIndex 来指定开始匹配位置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var re = /0/g,
re2 = /0/y,
str = "0102";
re.exec(str);
//["0", index: 0, input: "0102", groups: undefined]
re.lastIndex;
//0
re.exec(str);
//["0", index: 2, input: "0102", groups: undefined]
re.lastIndex;
//2
re.exec(str);
//null
re.lastIndex;
//0
re.lastIndex = 2;
re.exec(str);
//["0", index: 2, input: "0102", groups: undefined]
re.lastIndex;
//3

re2.exec(str);
//["0", index: 0, input: "0102", groups: undefined]
re2.exec(str);
//null
re2.lastIndex;
//0
re2.lastIndex = 2;
//2
re2.exec(str);
//["0", index: 2, input: "0102", groups: undefined]
re2.lastIndex;
//3
re2.exec(str);
//null
re2.lastIndex;
//0

还需要注意一点:

当使用带有 y 标识的匹配模式时,^断言总是会匹配输入的开始位置或者(如果是多行模式)每一行的开始位置。

1
2
3
4
5
6
7
8
9
var regex = /^foo/y;
regex.lastIndex = 2;
regex.test("..foo"); // false - 索引2不是字符串的开始

var regex2 = /^foo/my;
regex2.lastIndex = 2;
regex2.test("..foo"); // false - 索引2不是字符串或行的开始
regex2.lastIndex = 2;
regex2.test(".\nfoo"); // true - 索引2是行的开始
  • s(ES2018):singleline,我们知道.并不能匹配所有字符,而 s 修饰符的作用就是使.能匹配所有字符。
1
2
3
4
"1\n2".match(/.{3}/);
//null
"1\n2".match(/.{3}/s);
//["1↵2", index: 0, input: "1↵2", groups: undefined]

相关 API

RegExp

正则表达式通常用于匹配字符串中特定的字符串组合模式。
创建一个 RegExp 有两种方法:

  • /pattern/flags 字面量的形式。
  • new RegExp(pattern[,flags]) 调用构造函数的形式。
  • ES2015 中可以可以直接将字面量正则表达式传入 RegExp(语法拓展)。
1
2
3
4
5
6
/123/;
// /123/
new RegExp(/123/gim);
// /123/gim
new RegExp("123", "gim");
// /123/gim

传入非字符串会被隐式转换为字符串:

1
2
3
4
5
6
7
8
new RegExp(NaN);
// /NaN/
new RegExp(+Infinity);
// /Infinity/(带正号的数值忽略正号)
new RegExp(-Infinity);
// /-Infinity/
new RegExp(null);
// /null/

RegExp 方法

无自身方法,有实例方法。

RegExp 属性

  • RegExp.prototype 实例继承的属性
  • RegExp.length 只读,值为 2(我也不知道为什么等于 2)。
  • RegExp.$n,默认为空字符串,其值为上次调用正则匹配对应的第 n 个捕获项,通过这个我们可以不通过 RegExp 实例来获取捕获项的值
1
2
3
4
5
/(a)b(c)/.test("abc");
RegExp.$1;
//"a"
RegExp.$2;
//"c"

RegExp 实例方法

RegExp.prototype.exec(str)

对字符串执行 一次 正则匹配。返回结果数组或 null,并更新正则表达式实例对象的属性(lastIndex),匹配成功。
即使正则对象带有 g 标识也只进行一次正则匹配,因此返回的数组第一项为匹配项,后面为各个捕获项。
如果为全局匹配则会根据lastIndex属性指定位置开始下一次匹配。
通过 exec 方法返回的数组还包含以下几个属性:

  • input:原始输入字符串。
  • index:整个模式匹配成功的开始位置。
  • groups(ES2015),包含具名捕获项键值对的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var re3 = /\d/g;

re3.exec("123");
//["1", index: 0, input: "123", groups: undefined]
re3.exec("123");
//["2", index: 1, input: "123", groups: undefined]
re3.exec("123");
//["3", index: 2, input: "123", groups: undefined]
re3.exec("123");
//null
re3.exec("123");
//["1", index: 0, input: "123", groups: undefined]

var matchArr = /(\d)\w(\d)/.exec("a1b2c3");
//["1b2","1","2"]
matchArr.input;
//"a1b2c3"
matchArr.index;
//1

var reg = /a/g;
var myArray;
while ((myArray = reg.exec("aaa")) !== null) {
var msg = "Found " + myArray[0] + ". ";
msg += "Next match starts at " + reg.lastIndex;
console.log(msg);
}
reg.lastIndex;
//Found a. Next match starts at 1
//Found a. Next match starts at 2
//Found a. Next match starts at 3
//0
//连续多次调用exec方法查找同一字符串,会从上次匹配结果之后继续查找
//当返回结果为null时,lastIndex重置为0;

RegExp.prototype.test(str)

检测匹配输入是否有匹配项,返回 true,false。
如果正则为全局匹配,则和 exec 方法相同,根据 lastIndex 记录值开始匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var reg = /(\d)/g;
var str = "012";
reg.test(str);
//true
reg.lastIndex;
//1
RegExp.$1;
//0
reg.test(str);
//true
reg.lastIndex;
//2
RegExp.$1;
//"1"
reg.test(str);
//true
reg.lastIndex;
//3
RegExp.$1;
//"2"
reg.test(str);
//false
reg.lastIndex;
//0
RegExp.$1;
//"2" 最后一次的捕获值

RegExp.prototype.toString()

返回正则表达式的字符串形式,覆盖 Object.prototype.toString()。

1
2
/a/.toString();
//"/a/"

RegExp 实例属性

RegExp.prototype.constructor

指向实例构造函数 RegExp。

RegExp.prototype.flag

正则实例的修饰符字符串。

1
2
/123/gim.flags;
//"gim"

RegExp.prototype.global

只读,是否全局匹配。

RegExp.prototype.ignoreCase

只读,是否忽略大小写。

RegExp.prototype.lastIndex

下次开始匹配位置。

RegExp.prototype.multiline

只读,是否多行匹配。

RegExp.prototype.source

只读,实例对象的正则字符串。

1
2
/123/.source;
//"123"

RegExp.prototype.sticky(ES2015)

只读,是否粘滞匹配。

String.prototype.match([regexp])

返回一个数组,包含所有正则匹配项。如果没有匹配项,返回 null
regexp 是一个正则表达式对象,传入非正则表达式参数会 隐式转换(new RegExp(param)),不传参数等同于传入new RegExp()

如果正则表达式没有 g 标志,则 str.match() 会返回和 RegExp.exec() 相同的结果。而且返回的 Array 拥有一个额外的 input 属性,该属性包含被解析的原始字符串。另外,还拥有一个 index 属性,该属性表示匹配结果在原字符串中的索引(以 0 开始)。如果正则表达式包含 g 标志,则该方法返回一个 Array ,它包含所有匹配的子字符串而不是匹配对象。捕获组不会被返回(即不返回 index 属性和 input 属性)。如果没有匹配到,则返回 null 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"Abc123abc1234".match(/\d{3}/);
//["123", index: 3, input: "Abc123abc1234", groups: undefined]
"Abc123abc1234".match(/\d{3}/g);
//["123","123"]
"Abc123abc1234".match(/abc/g);
//["abc"]
"Abc123abc1234".match(/\d{3}/gi);
//["Abc", "abc"]
"Abc123abc1234".match();
//["", index: 0, input: "Abc123abc1234", groups: undefined]

"Abc123abc1234".match("123");
//["123", index: 3, input: "Abc123abc1234", groups: undefined]
//new RegExp("123") ==> /123/

String.prototype.replace(regexp|targetStr,newStr|function)

返回一个替换后的新字符串。
regexp 正则比匹配项会被第二个参数替换。
targetStr 第一个匹配项会被第二个参数替换。
newStr 可以插入特殊的变量名

变量名代表值
$$插入一个$
$&插入匹配的子字符串
$`插入当前匹配子串左边的内容
$’插入当前匹配子串右边的内容
$n加入第一个参数是 RegExp 对象,n 小于 100 且为非负整数,插入第 n 个括号匹配的字符串

function 作为第二参数,当匹配到匹配项时,便执行该函数。参数如下:

变量名代表值
match匹配的子串($&)
p1,p2,…,pn第 n 个捕获项的匹配子串($1,…,$n)
offset匹配项在字符串中的偏移值
string被匹配字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var testStr = "abc123abc123";

testStr.replace("abc", "rua"); //"rua123abc123"
testStr.replace(/abc/, "rua"); //"rua123abc123"
testStr.replace(/abc/g, "rua"); //"rua123rua123"

testStr.replace("abc", "$$"); //"$123abc123"
testStr.replace("abc", "$&rua"); //"abcrua123abc123"
testStr.replace("abc", "$'"); //"123abc123123abc123"
testStr.replace("abc", "$`"); //"123abc123"
testStr.replace(/abc/g, "$&rua"); //"abcrua123abcrua123"
testStr.replace(/abc/g, "$`"); //"123abc123123"

var testStr2 = "a b c d";

testStr2.replace(/(\w)\s(\w)/, "$2 $1"); //"b a c d"
testStr2.replace(/(\w)\s(\w)/, "$3 $2 $1"); //"$3 b a c d"
testStr2.replace(/(\w)\s(\w)/g, "$2 $1"); //"b a d c"

function replaceCallback(match, p1, p2, offest, string) {
return (
"match:" +
match +
";p1:" +
p1 +
";p2:" +
p2 +
";offset:" +
offest +
";string:" +
string +
";"
);
}
testStr2.replace(/(\w)\s(\w)/, replaceCallback);
//"match:a b;p1:a;p2:b;offset:0;string:a b c d; c d"

String.prototype.split([separator[,limit]])

separetor 表示分割字符串的标识,可以是字符串,正则表达式,忽略则返回包含字符串的数组。
limit 表示限制返回数组中分割字符串的 最大个数

1
2
3
4
5
6
"a;b;c".split(/;/);
//["a", "b", "c"]
"a;b;c".split();
//["a;b;c"]
"a;b;c".split(/;/, 2);
//["a", "b"]

String.prototype.search(regexp)

返回首次匹配的字符串索引,否则返回-1。
indexOf 不支持正则表达式。
忽略 g 标识。传入非 regexp 参数会进行隐式转换。

1
2
3
4
5
6
"abc".indexOf(/b/); 
//-1
"abc".search(/b/);
//1
"abc".search("b");
//1

参考链接