2022-03-13 06:14:05 +08:00

266 lines
12 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 正则表达式
createTime: 2022/03/08 06:32:01
permalink: /post/tjya08e9
author: pengzhanbo
top: false
type: # original: 原创: reprint 转载 可为空不填
tags:
- javascript
---
本文正则表达式基于`javascript`,不同的计算机语言对正则表达式的支持情况以及实现,语法不尽相同,不一定适用于其他语言。
<!-- more -->
### 简介
`正则表达式`是一种文本模式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/>当mn值为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
```