长沙黑马程序员 JS正则表达式语法详解

# JS正则表达式语法详解

## 前言

正则表达式在线图形化描述:https://regexper.com

什么是正则表达式?说白了,就是用来按照某种规则去匹配字符串的表达式

## RegExp对象

JS 通过内置对象 `RegExp` 支持正则表达式,有两种方法实例化 `RegExp` 对象

- 字面量
- 构造函数

## 字面量

范例:

```javascript
var reg = /\bis\b/
'He is a boy. This is a dog. Where is she?'.replace(reg, 'IS')
// 返回:'He IS a boy. This is a dog. Where is she?'

var reg = /\bis\b/g
'He is a boy. This is a dog. Where is she?'.replace(reg, 'IS')
// 返回:'He IS a boy. This IS a dog. Where IS she?'
```

补充说明:`\b` 是单词边界的意思

## 构造函数

范例:

```javascript
var reg = new RegExp('\\bis\\b')
'He is a boy. This is a dog. Where is she?'.replace(reg, 'IS')
// 返回:'He IS a boy. This is a dog. Where is she?'

var reg = new RegExp('\\bis\\b', 'g')
'He is a boy. This is a dog. Where is she?'.replace(reg, 'IS')
// 返回:'He IS a boy. This IS a dog. Where IS she?'
```

补充说明:

1. 第一个参数是正则表达式的源文本,第二个参数是修饰符
2. `\\bis\\b`,这里为什么要用两个反斜线?因为要当做普通的反斜线文本传递进去,所以需要转义

## 修饰符

- `g`:global 全文搜索,不添加,搜索到第一个匹配停止
- `i`:ignore case 忽略大小写,默认大小写敏感
- `m`:multiple lines 多行搜索

全文搜索和忽略大小写范例:

```javascript
'He is a boy. This is a dog. Is he?'.replace(/\bis\b/, '0')
// 返回:'He 0 a boy. This is a dog. Is he?'

'He is a boy. This is a dog. Is he?'.replace(/\bis\b/g, '0')
// 返回:'He 0 a boy. This 0 a dog. Is he?'

'He is a boy. This is a dog. Is he?'.replace(/\bis\b/gi, '0')
// 返回:'He 0 a boy. This 0 a dog. 0 he?'
```

多行搜索范例:

```javascript
'@123\n@456\n@789'.replace(/^@\d/g, 'X')
// 返回:`X123\n@456\n@789`

'@123\n@456\n@789'.replace(/^@\d/gm, 'X')
// 返回:`X123\nX456\nX789`
```

补充说明:`\n` 是换行转义字符

## 元字符

正则表达式由两种基本字符类型组成:

- 原义字符
- 元字符,元字符是在正则表达式中有特殊含义的非字母字符,如:`* + ? ^ $ . - | \ ( ) { } [ ] \t \v \n \r \f`

## 字符类

一般情况下正则表达式一个字符对应字符串一个字符

我们可以使用元字符 `[]` 来构建一个简单的类

所谓类就是一个泛指,而不是特指某个字符

表达式 `[abc]` 把字符 `a` 或 `b` 或 `c` 归为一类,可以匹配这三个字符

范例:

```javascript
'a1b2c3d4'.replace(/[abc]/g, 'X')
// 返回:'X1X2X3d4'
```

## 字符类取反

使用元字符 `^` 创建反向类

反向类的意思是不属于某类的内容

表达式 `[^abc]` 表示不是字符 `a` 或 `b` 或 `c` 的内容

范例:

```javascript
'a1b2c3d4'.replace(/[^abc]/g, 'X')
// 返回:'aXbXcXXX'
```

## 范围类

正则表达式还提供了范围类

我们可以使用 `[a-z]` 来连接两个字符表示从 `a` 到 `z` 的任意字符

这是个闭区间,也就是包含 `a` 和 `z` 本身

在 `[]` 组成的类内部是可以连写的,如:`[a-zA-Z]`

范例1:

```javascript
'a1b2d3x4z9'.replace(/[a-z]/g, 'Q')
// 返回:'Q1Q2Q3Q4Q9'
```

范例2:

```javascript
'a1b2d3x4z9AJHGYXG'.replace(/[a-zA-Z]/g, 'Q')
// 返回:'Q1Q2Q3Q4Q9QQQQQQQ'
```

范例3:

```javascript
'2019-12-15'.replace(/[0-9]/g, 'X')
// 返回:'XXXX-XX-XX'

'2019-12-15'.replace(/[0-9-]/g, 'X')
// 返回:'XXXXXXXXXX'
```

补充说明:

1. `-` 在 `[]` 里面是指某个范围区间的元字符,若想要把 `-` 也匹配到,就需要转义 `\-` 
2. 若 `-` 出现在字符类 `[]` 中,并且 `-` 左右两边都有字符,则为范围区间元字符,若任意一边无字符,则为原义字符 `-` 

## 预定义字符类

正则表达式提供预定义字符类来匹配常见的字符类:

- `.`,除了回车符和换行符之外的所有字符,等价于 `[^\r\n]` 
- `\d`,数字字符,等价于 `[0-9]` 
- `\D`,非数字字符,等价于 `[^0-9]` 
- `\s`,空白符,等价于 `[\t\v\r\n\f]` 
- `\S`,非空白符,等价于 `[^\t\v\r\n\f]` 
- `\w`,单词字符(字符、数字、下划线),等价于 `[a-zA-Z_0-9]` 
- `\W`,非单词字符,等价于 `[^a-zA-Z_0-9]` 

范例,匹配一个 `ab + 数字 + 单词字符` 的字符串:`/ab\d\w/` 

## 边界

正则表达式还提供了几个常用的边界匹配字符

- `^`,以...开始
- `$`,以...结束
- `\b`,单词边界
- `\B`,非单词边界

## 量词

如果我们希望匹配一个连续出现**20次**数字的字符串:

`/\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d/` 

这样写,未免太 lowb 了

正则表达式提供了量词:

- `?`,出现 0 次或 1 次
- `+`,出现 1 次或多次
- `*`,出现任意次
- `{n}`,出现 n 次
- `{n,m}`,出现 n 到 m 次
- `{n,}`,至少出现 n 次,注意 n 后面的逗号不能省

范例1:`\d{20}` 

范例2:`\d{20}\w\d?\w+\d*\d{3}\w{3,5}\d{3,}` 

## 贪婪模式

所谓贪婪模式,就是尽可能多的匹配,也就是说就算匹配成功还要继续往下尝试匹配更多满足条件的字符

正则表达式,默认就是贪婪模式

范例:

```javascript
'12345678'.replace(/\d{3,6}/, 'X')
// 返回:'X78'
```

## 非贪婪模式

所谓非贪婪模式,就是让正则表达式尽可能少的匹配,也就是说一旦成功匹配,本次便不再继续尝试匹配更多满足条件的字符

做法很简单,在量词后加上 `?` 即可

范例:

```javascript
'12345678'.replace(/\d{3,6}?/, 'X')
// 返回:'X45678'
```

## 分组

假设我们要匹配字符串 `Byron` 连续出现 3 次的场景,如果这样写:`/Byron{3}/`,是达不到我们想要的效果的,这里实际是 `n` 重复 3 次的意思

使用 `()` 可以达到分组的功能,让量词作用于分组,如:`/(Byron){3}/`,这样就可以满足我们的需求

范例:

```javascript
'a1b2c3d4'.replace(/[a-z]\d{3}/g, 'X')
// 返回:'a1b2c3d4'

'a1b2c3d4'.replace(/([a-z]\d){3}/g, 'X')
// 返回:'Xd4'
```

## 或

正则表达式中,使用 `|` 可以达到或的效果

范例:

```javascript
'ByronCasper'.replace(/Byron|Casper/g, 'X')
// 返回:'XX'

'ByronsperByrCasper'.replace(/Byr(on|Ca)sper/g, 'X')
// 返回:'XX'
```

## 反向引用分组

假设我们想要把 `2019-12-15` 改成 `12/15/2019`,就可以用反向引用分组轻松搞定

范例:

```javascript
'2019-12-15'.replace(/(\d{4})-(\d{2})-(\d{2})/g, '$2/$3/$1')
// 返回:'12/15/2019'
```

补充说明:其中 `$1`、`$2`、`$3`,表示直接引用第几个分组(也就是小括号)匹配到的字符

## 忽略分组

如果不希望捕获某些分组,只需要在分组内加上 `?:` 就可以了

范例(忽略年份这个分组):

```javascript
'2019-12-15'.replace(/(?:\d{4})-(\d{2})-(\d{2})/g, '$2/$3/$1')
// 返回:'15/$3/12'
```

## 前瞻后瞻

正则表达式从文本头部向尾部开始解析,文本尾部方向,称为 **前**

前瞻就是在正则表达式匹配到规则的时候,向前检查是否符合断言,后瞻方向相反

符合和不符合特定断言称为 **肯定** 匹配和 **否定** 匹配

ES9才支持后瞻

语法如下:

- 肯定前瞻,`exp(?=assert)`
- 否定前瞻,`exp(?!assert)`
- 肯定后瞻,`exp(?<=assert)`,ES9才支持
- 否定后瞻,`exp(?

范例:

```javascript
'a2*3'.replace(/\w(?=\d)/g, 'X')
// 返回:'X2*3'

'a2*34v8'.replace(/\w(?=\d)/g, 'X')
// 返回:'X2*X4X8'

'a2*34vv'.replace(/\w(?=\d)/g, 'X')
// 返回:'X2*X4vv'

'a2*34vv'.replace(/\w(?!\d)/g, 'X')
// 返回:'aX*3XXX'
```

## 字符串方法

### String.prototype.search

`search` 方法用于检索字符串中指定的子字符串,或检索与正则表达式匹配的子字符串

方法返回第一个匹配结果的索引,匹配不到返回 **-1**

`search` 方法不执行全局匹配,它将忽略修饰符 `g`,并且总是从字符串的开始进行检索

范例:

```javascript
'a1b2c3d1'.search('1')
// 返回:1

'a1b2c3d1'.search('10')
// 返回:-1

'a1b2c3d1'.search(1)
// 返回:1

'a1b2c3d1'.search(/1/)
// 返回:1

'a1b2c3d1'.search(/1/g)
// 返回:1
```

### String.prototype.match(reg)

`match` 方法将检索字符串,以找到一个或多个与 `reg` 匹配的文本

`reg` 是否具有修饰符 `g`,对结果影响很大

- 没有修饰符 `g`:
  - 如果 `reg` 没有修饰符 `g`,那么 `match` 方法就只能在字符串中执行一次匹配

  - 如果没有找到任何匹配的文本,将返回 `null`,否则它将返回一个数组,其中存放了与它找到的匹配文本有关的信息

  - 返回数组的第一个元素存放的是匹配文本,而其余的元素存放的是与正则表达式的分组匹配的文本

  - 除了常规的数组元素之外,返回的数组对象还包含两个额外的属性

    - `index`:匹配文本的起始字符在字符串中的位置
    - input:源字符串的引用

  - 范例:

    ```javascript
    var reg = /\d(\w)\d/
    var str = '$1a2b3c4d5e'
    var result = str.match(reg)
    console.log(result)
    // 输出:['1a2', 'a']
    console.log(result.index, ',', result.input)
    // 输出:1 , $1a2b3c4d5e
    ```

- 有修饰符 `g`:

  - 如果 `reg` 具有修饰符 `g`,则 `match` 方法执行全文检索,找到字符串中所有的匹配文本

  - 如果没有找到任何匹配的文本,则返回 `null`,如果找到了一个或多个匹配的文本,则返回一个数组

  - 数组元素中存放的是字符串中所有的匹配文本,并且该数组对象没有 `index` 和 `input` 两个额外的属性

  - 范例:

    ```javascript
    var reg = /\d(\w)\d/g
    var str = '$1a2b3c4d5e'
    var result = str.match(reg)
    console.log(result)
    // 输出:['1a2', '3c4']
    console.log(result.index, ',', result.input)
    // 输出:undefined , undefined
    ```

    

### String.prototype.split

我们经常使用 `split` 方法把字符串分割为字符串数组,如:

```javascript
'a,b,c,d'.split(',')
// 返回:['a', 'b', 'c', 'd']
```

但是在一些复杂的分割情况下,我们就需要使用正则表达式解决,如:

```javascript
'a1b2c3d'.split(/\d/)
// 返回:['a', 'b', 'c', 'd']
```

### String.prototype.replace

我们知道 `replace` 方法是用来替换字符串的,但其实它可以用正则表达式来替换

范例:

```javascript
'a1b'.replace('1', 2)
// 返回:a2b

'a1b1c1'.replace('1', 2)
// 返回:a2b1c1

'a1b1c1'.replace(/1/g, 2)
// 返回:a2b2c2

'a1b1c1'.replace(/1/, 2)
// 返回:a2b1c1
```

补充说明:

- 第二个参数也可以接收一个函数,函数的参数列表如下:

  - 匹配的字符串
  - 正则表达式分组内容,没有分组则没有该参数
  - 匹配项在字符串中的索引
  - 源字符串

- 下面用正则表达式有无分组来分别演示:

  - 无分组范例:

    ```javascript
    'a1b2c3d4e5'.replace(/\d/g, function (matchedStr, index, origin) {
      console.log(index, ',', origin)
      return parseInt(matchedStr) + 1
    })
    // replace后,最终返回:a2b3c4d5e6
    // 输出如下:
    // 1 , a1b2c3d4e5
    // 3 , a1b2c3d4e5
    // 5 , a1b2c3d4e5
    // 7 , a1b2c3d4e5
    // 9 , a1b2c3d4e5
    ```

  - 有分组范例:

    ```javascript
    'a1b2c3d4e5'.replace(/(\d)(\w)(\d)/g, function (matchedStr, group1Str, group2Str, group3Str, index, origin) {
      console.log(matchedStr, ',', group1Str, ',', group2Str, ',', group3Str, ',', index, ',', origin)
      return group1Str + group3Str
    })
    // replace后,最终返回:a12c34e5
    // 输出如下:
    // 1b2 , 1 , b , 2 , 1 , a1b2c3d4e5
    // 3d4 , 3 , d , 4 , 5 , a1b2c3d4e5
    ```

你可能感兴趣的:(前端,it,程序员)