正则表达式是一个描述规则的表达式,它可以匹配模式,匹配字符,匹配位置,进而实现搜索和替换功能。

1. 匹配规则

1.1 单个字符

单个字符的映射关系是一对一的,匹配到的字符只有一个。

匹配类型 正则表达式
简单字符 a (匹配字符a)
特殊字符 \* (匹配字符*,特殊字符用\转义)
换行符 \n
换页符 \f
回车符 \r
空白符 \s
制表符 \t
垂直制表符 \v
回退符 [\b]

1.2 区间

[] 来表示集合,用 - 来表示去区间范围,就可以实现一对多的匹配。
/[123]/ 匹配1,2,3中的任意一个字符,/[0-9]/ 匹配0到9的任意一个数字,/[a-z]/ 匹配任意一个英文小写字母。

匹配区间 正则表达式 记忆方式
集合区间 [123] 匹配一个字符,可以是1,2,3之一
通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分隔符除外。 .
单个数字,[0-9] \d digit
除了[0-9] \D not digit
包括下划线在内的单个字符[A-Za-z0-9] \w word
非单字字符 \W not word
匹配空白符,包括空格、制表符、换行符和换页符 \s space
匹配非空白字符 \S not space
匹配任意字符 [\d\D]、[\w\W]、[\s\S]

1.3 量词

多次循环,重复上面的正则规则,就可以匹配匹配多个字符。

符号 重复次数 例子
? 0次或1次 /colour?/ 匹配colou和colour两个单词
+ 1次或多次 /color+/ 匹配color, colorr, colorrr…
{n} 区间次数 /a{3}/ 连续出现a 3次
{min,max} min到max次 /a{1,2}/ 连续出现a 1-2次
{min,} 至少min次 /a{1,}/ 连续出现a至少1次
{0, max} 最多max次 /a{0,3}/ 连续出现a最多3次
* 匹配0个字符或无数个字符 /*/ 用来过滤可有可无的字符串

1.5 位置符

位置是相邻字符之间的位置。

符号 含义 例子
\b 单词边界,\w和\W之间的位置,也包括\w和^,\w和$之间的位置 /\bcat\b/ 匹配cat,匹配不到scattered
^ 匹配开头,在多行匹配中匹配开头 /^I am/ 以I am开头的字符串
$ 匹配结尾,在多行匹配中匹配结尾 /^I am good.$/ 匹配I am good这个句子
(?=p) p是一个子模式,表示p前面的位置 /(?=l)/g 所有l前面的位置
(?!=p) (?=p)的反面意思
1
2
3
4
"hello".replace(/^|$/g, '#')              // "#hello#"
"[JS] lesson_01.mp4".replace(/\b/g, '#') // "[#JS#] #lesson_01#.#mp4#"
"hello".replace(/(?=l)/g, "#") // "he#l#lo"
"hello".replace(/(?!=l)/g, "#") // "#h#ell#o#"

1.4 分组,回溯引用

() 表示一个分组,只是使用简单的(regex)本质上和不分组是一样的,要发挥它强大的作用,要结合回溯引用。

\1, \2.... 表示引用的第1,2个子表达式… \0 表示整个表达式。回溯引用 指后面引用前面已经匹配到的子字符串。

$1, $2... 用于引用要被替换的字符串

1
2
3
4
var reg = /\b(\w)+\s\1/   // 匹配两个连续相同的单词

var str = 'abc abc 123';
str.replace(/(ab)c/g, '$1g'); // 'abg abg 123'

$& 匹配子串文本

1
2
// '2,3,5'变成'222,333,555'
"2,3,5".replace(/(\d)+/g, $&$&$&); // 222,333,555

(?:regex),如果我们不想子表达式被引用,可以用非捕获正则, 这样可以避免浪费内存。

1
2
3
var str = 'scq000';
str.replace(/(scq00)(?:0)/, '$1,$2'); // 'scq00,$2'
// 使用了非捕获正则,所以第二个引用没有值,这里直接替换为$2

1.5 逻辑处理

逻辑关系 正则表达式
[^regex] 和 !
|

1.6 修饰符

修饰符放在了/regex/img正则表达式的最后面。

修饰符
m 多行匹配 multiline
i 忽略大小写 ignore
g 全局匹配global, 在目标字符串中按顺序找到满足匹配模式的所有子串
1
2
3
4
var regex = /ab{2,4}c/g
var string = "abc abbc abbbc abbbbc abbbbbc";
console.log(string.match(regex));
// ["abbc", "abbbc", "abbbbc"]

2. 正则表达式 + JS

有了正则表达式进行匹配,结合JS api,我们可以完成验证、切分、提取、替换的动作。

1
2
3
4
5
6
String.search(regex)
String.split(regex)
String.match(regex)
String.replace(regex, newText)
RegExp.test(string)
RegExp.exec(string)

2.1 验证

判断有没有匹配上

1
2
3
4
5
6
// 判断一个字符串中有没有数字
var regex = /\d/;
var string = "abc123";

// 最常用的是test
console.log(regex.test(string)); // true

2.2 切分

把目标字符串,切成一段一段 string.split(regex)

1
2
3
4
5
// 日期格式切除年月日
var regex = /\D/;
console.log("2017-01-01".split(regex)); // ["2017", "01" "01"]
console.log("2017.01.01".split(regex)); // ["2017", "01" "01"]
console.log("2017/01/01".split(regex)); // ["2017", "01" "01"]

2.3 提取

提取匹配到的数据

1
2
3
4
5
var regex = /^(\d{4})\D(\d{2})\D(\d{2}))$/;
var string = "2017-06-26";
string.match(regex);
// [$0, $1, $2 ... , index, input]
// =>["2017-06-26", "2017", "06", "26", index: 0, input: "2017-06-26"]

2.4 替换

string.replace(regex, newtext)

1
2
3
4
// 将yyyy-mm-dd替换成yyyy/mm/dd
var string = "2017-06-26";
var today = new Date( string.replace(/\-/g, "/"))
console.log(today) // => Mon Jun 26 2017 00:00:00 GMT+0800 (中国标准时间)

3. 匹配原理

3.1 回溯

后补

3.2 操作符优先级

优先级从上至下,由高到低

  • 转义符 \
  • 括号和方括号 (…)、(?:…)、(?=…)、(?!…)、[…]
  • 量词限定符 {m}、{m,n}、{m,}、?、*、+
  • 位置和序列 ^ 、$、 \元字符、 一般字符
  • 管道符(竖杠)|

3.3 优化效率

后补

4. 例子

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
var regex = /hello/;
regex.test("hello");
// true


var regex = /ab{1,2}c/g;
"abc abbc abbbc".match(regex);
// ["abc", "abbc"]


var regex = /a[123]b/g;
"a0b a1b a4b".match(regex);
// ["a1b"];


// 匹配2-3位数
// 筛选数组中的三位数 arr.join(" ").match(/d{3}/g)
// 贪婪匹配,尽可能多的匹配
var regex = /\d{2,5}/g;
"123 1234 12356 123456".match(regex);
// ["123", "1234", "1234", "12345"]


// 惰性匹配,尽可能少的匹配
// 在量词后面加个问号能实现惰性匹配 {m,n}? {m,}? ?? +? *?
var regex = /\d{2,3}?/g
"123 1234 12356 123456".match(regex);
// ["12", "12", "34", "12", "34", "12", "34", "56"]


// (p1|p2|p3)分支结构是惰性的,前面的匹配上了,后面的就不再尝试
var regex = /good|goodbye/g; // ["good"]
var regex = /goodbye|good/g; // ["goodbye"]
"goodbye".match(regex);


// 匹配16进制颜色值, [0-9a-fA-F]出现3或6次
var regex = /#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})/g;;
var string = "#ffbbad #FC01df #fff";
// ["#ffbbad","#FC01df","#fff"]


// 匹配时间
var regex = /^([01][0-9]|[2][0-3]):([0-5][0-9])$/;
regex.test("23:59")
// true


// 匹配日期
var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/
regex.test("2019-01-02");
// true



// 把yyyy-mm-dd格式替换成mm/dd/yyyy
var regex = /(\d{2})-(\d{2})-(\d{2})/;
"2019-01-02".replace(regex, "$2/$3/$1");
// "01/02/2019", 等价于
var result = "2019-01-02".replace(regex, function(match, year, month, day){
return month + "/" + day + "/" + year;
})


// 匹配2016-01-02 2016/01/01 2016.01.02三种格式,确保前后分隔符一致
var regex = /\d{4}(-|/|.)\d{2}\1\d{2}/;
regex.test(string); // true



// 匹配windows操作系统文件路径
var regex = /^[a-zA-Z]:\\([^\\:*<>|"?\r\n/]+\\)*([^\\:*<>|"?\r\n/]+)?$/;
console.log( regex.test("F:\\study\\javascript\\regex\\regular expression.pdf") );
console.log( regex.test("F:\\study\\javascript\\regex\\") );
console.log( regex.test("F:\\study\\javascript") );
console.log( regex.test("F:\\") );
// true


// 匹配id值
var regex = /id=".*"/; // id="container" class="main"
var regex = /id="[^"]*"/; // id="container"
var string = '<div id="container" class="main">'


// 数字千分位分隔符
var regex = /(?!^)(?=(\d{3})+$)/;
"12345678".replace(regex, ',') // 12,345,678
"123456789".replace(regex, ',') // 123,456,789


// 密码 6-12位 多重规则应拆开写
// 1. 同时包含数字和小写字母
// 2. 同时包含数字和大写字母
// 3. 同时包含小写字母和大写字母
// 4. 同时包含数字、小写字母和大写字母
var regex1 = /^[0-9a-z]{6-12}$/
var regex2 = /^[0-9A-Z]{6-12}$/
var regex3 = /^[a-zA-Z]{6-12}$/
var regex4 = /^[0-9a-zA-Z]{6-12}$/


// 匹配浮点数
// 10 +10 -10 1.23 +1.23 -1.23 .2 +.2 -.2
var regex = /^[+-]?(\d+|\d+\.\d+|.\d+)$/


// 字符串trim模拟
function trim(str) {
return str.replace(/^\s+|\s+$/g, '');
}

// 将每个单词的首字母转为大写
function titleize(str) {
retrun str.toLowerCase().replace(/(?:^|\s)\w/g, function(c){
return c.toUpperCase();
})
}
titleize("my name is amy") // "My Name Is Amy"


// 驼峰化
// ?的目的,为了应对str尾部的字符可能不是单词字符 "-moz-transform "
function camelize(str) {
return str.replace(/[-_\s]+(.)?/g, function(match, c){
return c ? c.toUpperCase() : ''
})
}

// 中划线化
function dasherize(str) {
return str.replace(/([A-Z])/g, '-$1').replace(/[-_/s]+/g, '-').toLowerCase();
}

// html转义和反转义
function unescapeHTML(str) {
var htmlEntities = {
lt: '<',
gt: '>',
nbsp: ' ',
cent: '¢',
pound: '£',
yen: '¥',
euro: '€',
copy: '©',
reg: '®',
quot: '"',
amp: '&',
apos: '\''
}

return str.replace(/\(&[^;]+);/, function(match, key){
if (key in htmlEntities) {
return htmlEntities[key]
}
return match;
})
}
unescapeHTML('&lt;div&gt;Blah blah blah&lt;/div&gt'); // "<div>Blah blah blah</div>"


// 符合 成对标签 格式
var regex = /<([^>]+)>[\d\D]*<\/\1>/;
regex.test("<title>wrong!</p>")


// 保证只有中文
var regex = /^[\u4e00-\u9fa5]+$/ig;
regex.test('中文') // true
regex.test('中a') // false

5. 没有必要用正则的情况

1
2
3
4
5
6
7
8
9
// 从日期中提取年月日
"2017-01-02".spilt('-') // ["2017", "01", "02"]

// 判断是否有问号
"123?123".indexOf('?') // 3
"123".indexOf('?') // -1

// 获取子串
"JavaScript".substring(4);

参考资料