---
title: 正则表达式
lang: zh-CN
createTime: 2018/11/26 11:15:27
permalink: /article/e8qbp0dh
author: pengzhanbo
tags: 
  - javascript
top: false
type: null
---

_本文正则表达式基于`javascript`，不同的计算机语言对正则表达式的支持情况以及实现，语法不尽相同，不一定适用于其他语言。_

### 简介
`正则表达式`，是一种文本模式（Regular Expression），是对字符串的一种匹配查找规则。可以方便的在某一文本字符串中，查找、定位、替换符合某种规则的字符串。

比如说，我们想要找出一段文本中的手机号码，文本内容如下：
``` html
name:Mark  tel:13800138000
name:Jhon  tel:13800138888
```
很明显，在这段文本中，手机号码是以 `tel:`开头，这符合一定的规则，这样我们可以通过正则表达式来书写这个规则，然后去查找匹配：
``` js
var text = `name:Mark  tel:13800138000
name:Jhon  tel:13800138888`;

var result = text.match(/tel:(1\d{10})/);
// ["tel:13800138000", "13800138000", index: 0, input: "tel:13800138000", groups: undefined]
var tel = result[1];
// 13800138000
``` 
`/tel:(1\d{10})/` 便是所说的正则表达式。

### `RegExp`与字面量
在`javascript`中，我们可以使用构造函数`RegExp` 创建正则表达式。

`new RegExp(pattern[, flags])`
``` js
var regExp = new RegExp('\\d', 'g');
```
也可以通过 字面量的方式：
``` js
var regExp = /\d/g;
```

两种创建正则表达式适用的场景有些细微的不同，一般使用`new RegExp()`来创建动态的正则表达式，使用字面量创建静态的正则表达式。

正则表达式字面量是提供了对正则表达式的编译，当正则表达式保持不变时，用字面量的方式创建正则表达式可以获得更好的性能。

_以下讨论以正则表达式字面量来创建正则表达式_

`正则表达式`一般由`元字符`和普通字符组成。

### 元字符
元字符也叫特殊字符，是正则表达式规定的，对符合特定的单一的规则的字符的描述。
| 字符 | 含义 |
|:----:|-----|
|\\|在非特殊字符的前面加反斜杠，表示这个字符是特殊的，不能从字面上解释。比如在`\d`描述的不是一个普通的字符`d`,而是正则表达式中的数值`0-9`。<br/>如果在特殊字符前面加反斜杠，这表示将这个字符转义为普通字符，比如`?`在正则中有其特殊含义，前面加反斜杠\?，这可以将其转为普通的`?`。|
|^|匹配文本开始的位置，如果开启了多行标志，也会匹配换行符后紧跟的位置。<br/>比如`^a`会匹配`abc`，但不会匹配到`bac`。|
|$|匹配文本结束的位置，如果开启了多行标志，也会匹配换行符前紧跟的位置。<br/>比如`b$`会匹配`acb`,但不会匹配到`abc`。|
|*|匹配前一个表达式0次到多次。<br/>比如，`ab*`会匹配到`abbbbbbc`中的`abbbbbb`,以及`acbbbbb`中的`a`。|
|+|匹配前一个表达式1次到多次。<br/>比如，`ab+`会匹配到`abbbbbbc`中的`abbbbbb`,但不会匹配`acbbbbb`。|
|?|匹配前一个表达式0次到1次。<br/>比如，`ab*`会匹配到`abbbbbbc`中的`ab`,以及`acbbbbb`中的`a`。|
|.| 匹配除换行符之外的任何单个字符。|
|x\|y| 匹配 x或者y。| 
|\[xyz\]| 表示一个字符的集合。匹配集合中的任意一个字符。可以使用破折号`-`来指定一个字符范围。<br/>比如，`[0-4]`和`[01234]`，都可以匹配`4567`中的`4`。|
|\[^xyz\]| 表示一个方向字符集合。匹配任意一个不包括在集合中的字符。可以使用破折号`-`来指定一个字符范围。<br/>比如，`[0-4]`和`[01234]`，都可以匹配`2345`中的`5`。|
|{n}|n为一个整数，表示匹配前一个匹配项n次。<br/>比如`a{2}`不会匹配`abc`中的`a`，但会匹配`aaaabc`中的`aa`。|
|{m,n}| m,n都是一个整数，匹配前一个匹配项至少发生了m次，最多发生了n次。<br/>当m，n值为0时，这个值被忽略，当n值不写，如`{1,}`表示1次到多次。当m值不写时，如`{,1}`表示0次到1次。|
|(x)|匹配`x`并且捕获该匹配项。称为捕获括号，括号中的匹配项也称作子表达式。|
|(?:x)| 匹配`x`但不捕获该匹配项。称为非捕获括号。|
|x(?=y)| 匹配`x`且当`x`后面跟着`y`。称为正向肯定查找（正向前瞻）。|
|x(?!y)| 匹配`x`且当`x`后面不跟着`y`。称为正向否定查找（负向前瞻）。|
|[\b]|匹配一个退格(U+0008)。|
|\b	|匹配一个词的边界。匹配的值的边界并不包含在匹配的内容中。|
|\B|匹配一个非单词的边界。|
|\d|匹配一个数字。等价于`[0-9]`。|
|\D|匹配一个非数字。等价于`[^0-9]`。|
|\n|匹配一个换行符 (U+000A)。|
|\r|匹配一个回车符 (U+000D)。|
|\s|匹配一个空白字符，包括空格、制表符、换页符和换行符。<br/>等价于`[ \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]`|
|\S|匹配一个非空白字符。|
|\t|匹配一个水平制表符 (U+0009)。|
|\w|匹配一个单字字符（字母、数字或者下划线）。<br/> 等价于`[A-Za-z0-9_]`。|
|\W|匹配一个非单字字符。|
|\xhh|与代码 hh 匹配字符（两个十六进制数字）|
|\uhhhh|与代码 hhhh 匹配字符（四个十六进制数字）。|


上表在多数文章都会提及，但有一些注意的细节，下面我单独拎出来说说。

1. __`[xyz]` 匹配集合中的任意一个字符__ <br/>
这个字符集的元素，可以是普通字符，也可以是特殊字符，也可以用破折号`-`规定一个字符集范围。<br/>
以匹配数字为例，可以写成`[0123456789]` ，也可以写成`[\d]`,也可以写成`[0-9]`。<br/>
类似于`()`等特殊字符，在`[]`中有其作用，都特殊字符的作用一致，不能直接当做普通字符来使用，所以我们需要使用反斜杠`\`将其转义为普通字符，值得注意的是，上表的特殊字符中，星号`*`、小数点`.`在`[]`中并没有特殊用途，所以不需要做转义处理，当然，即使做了转义，也不会出现问题；而破折号`-`在`[]`中有其特殊作用，所以作为普通字符使用时，需要转义。

2. __`?`：匹配前一个表达式0次到1次。__<br/>
其实这里准确描述来说，匹配前一个表达式，且该表达式 __非任何量词 `*`、 `+`、`?` 或 `{}`__ ，匹配前一个表达式0次到1次。<br/>
如果紧跟在 __非任何量词 `*`、 `+`、`?` 或 `{}`__ 的后面，将会使量词变为非贪婪的（匹配尽量少的字符）<br/>
_贪婪与非贪婪匹配，我们在下文细说。_

### 等价字符

正则表达式中，有不少特殊字符的写法，是等价的，也可以说是简写形式，下表的左右两边，都是等价的。
|regExp|regExp|
|--|--|
|*|{0,}|
|+|{1,}|
|?|{0,1}|
|\d|\[0-9\]|
|\D|\[^0-9\]|
|\w|\[a-zA-Z_\]|
|\W|\[^a-zA-Z_\]|
|\s|\[ \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\]|

### 贪婪模式与非贪婪
__什么是贪婪模式？__

贪婪是指正则表达式匹配时，是贪心的，会尽可能的匹配多的字符，主要体现在`量词特殊字符`：
``` js
// 匹配一个到多个数字
var r = /\d+/;
var t1 = '12a';
var t2 = '1234a';
var t3 = 'a12b345';
console.log(t1.match(r)[0]); // 12
console.log(t2.match(r)[0]); // 1234
console.log(t3.match(r)[0]); // 12
```
`非贪婪`，即是让正则表达式匹配尽量少的字符。那么如何改变正则表达式的贪婪模式？

__在量词特殊字符后面紧跟使用`?`__

我们说说的量词包括`*`, `+`, `?`, `{m,n}`。那么紧跟了`?`，会有什么不同的表现呢？

我们从例子来分析：
``` js 
var r1 = /<div>.*<\/div>/;
var r2 = /<div>.*?<\/div>/;
var str = '<div>aaa</div>bbb<div></div>ccc';
```
变量`r1`是贪婪匹配，得到的结果会是什么呢？
``` js
console.log(str.match(r1)[0]);
// <div>aaa</div>bbb<div></div>
```
在这段字符串中，有两个`</div>`的匹配字符串，正则表达式在遇到第一个`</div>`匹配字符项时，同时满足了`/.*/`和`/<\/div>/`的匹配条件，优先作为`/.*/`的匹配值，在遇到第二个时，同样还是优先作为`/.*/`的匹配值，直到匹配的字符串`str`的结束，没有满足条件的匹配字符串，再把第二个`</div>`作为`/<\/div>/`的匹配值。最终得到了`<div>aaa</div>bbb<div></div>`的匹配结果。

变量`r2`这是非贪婪匹配，得到的结果又会有所不同：
``` js
console.log(str.match(r1)[0]);
// <div>aaa</div>
```
同样，两个`</div>`的匹配字符串，但实际非贪婪匹配模式，在匹配到第一个`</div>`，就不会再继续向下匹配字符串了。

__也就是说，贪婪匹配是，在满足规则下，尽可能多的匹配更多的字符串，直到字符串结束或没有满足规则的字符串了；非贪婪匹配是，在满足规则下，尽可能少的匹配最少的字符串，一旦得到满足规则的字符串，就不再向下匹配。__

1. `x*?`：尽可能少的匹配`x`，匹配的结果可以是0个`x`；
2. `x+?`：尽可能少的匹配`x`，但匹配的结果至少有1个`x`；
3. `x??`：尽可能少的匹配`x`, 匹配的结果可以是0个`x`，但最多可以有一个`x`；
4. `x{m,n}?`：尽可能少的匹配`x`，但匹配的结果至少有m个`x`，最多可以有n个`x`；

可能从字面来说，不好理解 `x??`, `x{m,n}?` ，来看一个例子就可以明白了：
``` js
var s1 = '<div>aa</div>';
var s2 = '<div>a</div>';
var s3 = '<div></div>';
var r1 = /<div>a??<\/div>/;

console.log(r1.test(s1)); // false 
console.log(r1.test(s2)); // true
console.log(r1.test(s3)); // true
```
``` js
var s1 = '<div>aaa</div>';
var s2 = '<div>aa</div>';
var s3 = '<div>aaaa</div>'
var r1 = /<div>a{2,3}?<\/div>/;
var r2 = /<div>a{2,3}?/;

console.log(r1.test(s1)); // true
console.log(r1.test(s2)); // true
console.log(r1.test(s3)); // false

console.log(s1.match(r2)[0]); // <div>aa
console.log(s2.match(r2)[0]); // <div>aa
console.log(s3.match(r2)[0]); // <div>aa
```

### 正则表达式标志
|标志|描述|
|:--:| -- |
|g|全局搜索|
|i|不区分大小写搜索|
|m|多行搜索|
|y|执行“粘性”搜索,匹配从目标字符串的当前位置开始|
|u|Unicode模式。用来正确处理大于 \uFFFF 的Unicode字符|

__`m`__

使用`m`标志时，会改变开始（`^`）和结束字符（`$`）的工作模式，变为在多行上匹配，分别匹配每一行的开始和结束，即`\n`或`\r` 分割。

__`y`__

使用`y`标志时，匹配是从`RegExp.lastIndex`指定的位置开始匹配，匹配为真时，会修改`lastIndex`的值到当前匹配字符串后的位置，下次匹配从这个位置开始匹配，如果匹配为假时，不会修改`lastIndex`的值。
``` js
let reg = /o/y;
let str= 'foo';

// lastIndex 为 0，从字符 f 开始匹配  
reg.test(str); // false
// 由于结果为 false， lastIndex 还是为 0
reg.test(str); // false

let str2 = 'oof';
// lastIndex 为 0 ，从字符 o 开始匹配
reg.test(str2); // true
//  lastIndex 此时修改为 1, 从第二个 o 开始匹配
reg.test(str2); // true
// lastIndex 此时修改为 2
reg.test(str2); // false   此时开始匹配的字符是 f
// lastIndex没有被修改
reg.test(str2); // false
```


### 正则表达式中的捕获—— \1,\2,\3... 以及 $1,$2,$3...

在上文中我们介绍了 `(x)` 是匹配 `x` 并捕获，那么有了捕获就必然可以去使用捕获到的结果， `\1,\2,\3...` 以及` $1,$2,$3...` 便是指捕获的结果。

`\1, \2, \3, \4, \5, \6, \7, \8, \9` 在正则表达式中使用，捕获结果为正则表达式的源模式.

在这个正则表达式中`(bc)`被捕获并标记为`\1`, `(ef)`被捕获并标记为`\2`。
``` js
let reg = /a(bc)d(ef)/
```
也可以使用来简化正则表达式
``` js
let reg = /a(bc)dbc/
let reg2 = /a(bc)d\1/

let str = 'abcdbc';

reg.test(str); // true
reg2.test(str); // true
```

`$1, $2, $3, $4, $5, $6, $7, $8, $9` 是`RegExp`的包含括号子表达式的正则表达式静态的只读属性。
``` js
let reg = /a(bc)d/;
let str = 'abcd';

reg.test(str);
console.log(RegExp.$1); // bc
```
在 `String.replace()` 中使用：
``` js
let reg = /(\w+)\s(\w+)/;
let str = 'apple pear';
str.replace(reg, '$2 $1'); // pear apple
RegExp.$1; // apple
RegExp.$2; // pear
```
