> For the complete documentation index, see [llms.txt](https://lizh.gitbook.io/knowledge/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lizh.gitbook.io/knowledge/javascript-xi-lie/01-ji-chu-pian-04-zheng-ze-biao-da-shi.md).

# 基础篇 04 正则表达式

正则表达式（Regular Expression）是用于匹配字符串中字符组合的模式。在 JavaScript 中，正则表达式也是对象。这些正则表达式可用于 RegExp 的 exec、test 方法，以及 String 的 match、matchAll、replace、replaceAll、search 和 split 方法。

一个正则表达式是由简单的字符所构成的，比如，`/abc/`；或者是简单和特殊字符的组合，比如 `/ab+c/` 或 `/Chapter (\d+)\.\d*/`，其中 `+ . *` 都是有特殊意义的字符。

## RegExp对象

RegExp 是一个 JavaScript 内置的对象，也可以当作构造地函数用于创建正则表达式对象。

### 创建正则表达式

创建正则表达式对象有三种方法：字面量、构造函数和普通函数。

字面量创建正则表达式，参数用**斜杠**括起来，不使用引号：

```javascript
/pattern/flags
```

```javascript
const regExp1 = /ab+c/;
const regExp2 = /ab+c/ig;
```

RegExp 构造函数，参数不包含在斜杠之间，但使用引号：

```javascript
new RegExp(pattern[, flags])
```

```javascript
const regExp1 = new RegExp("ab+c");
const regExp2 = new RegExp(/ab+c/);
const regExp3 = new RegExp(/ab+c/, 'ig');
```

RegExp 函数，参数与构造函数相同：

```javascript
RegExp(pattern[, flags])
```

```javascript
const regExp1 = RegExp("ab+c");
const regExp2 = RegExp(/ab+c/);
const regExp3 = RegExp(/ab+c/, 'ig');
```

pattern 是正则表达式的文本，文本可以包含特殊字符来匹配比字面值字符串更广泛的值范围。从 ES5 开始，pattern 也可以是另一个 RegExp 对象，执行后，该对象的 flags 和 lastIndex 将被复制。

flags 是可选参数，用于指定要添加的标志的字符串。从 ES6 开始，如果 pattern 是一个对象，flags 字符串将替换该对象的任何标志，并且 lastIndex 将重置为 0。

**注意：** 每一次正则表达式都是一个 RegExp 实例对象。

```javascript
const a = /123/;
const b = /123/;
console.log(typeof a) // 'object'
console.log(typeof b) // 'object'
console.log(a == b)   // false
console.log(a === b)  // false
```

### flags标志

flags 可包含下列任何字符的组合：

* **g （全局匹配）：** 找到所有的匹配，而不是在第一个匹配之后停止。
* **i （忽略大小写）：** 如果 u 标志也被启用，使用 Unicode 大小写折叠。
* **m （多行匹配）：** 将开始（`^`）和结束字符（`$`）视为在多行上工作。换句话说，匹配每一行的开头或结尾（由 `\n` 或者 `\r` 分隔），而不仅仅是整个输入字符串的开头或结尾。
* **s （点号匹配所有字符）：** 允许 `.` 去匹配新的行，包括行终结符：U+000A 换行符（"\n"）、U+000D 回车符（"\r"）、U+2028 行分隔符、U+2029 段分隔符。
* **u （unicode）：** 将正则表达式文本视为 Unicode 码位序列。
* **y （sticky，粘性匹配）：** 仅从正则表达式的 lastIndex 属性表示的索引处搜索。 如果一个表达式同时指定了 `y` 和 `g`，将会忽略 `g` 标志。

  ```javascript
  const str = 'abcab3ab6ab9'
  const regex = /ab/y

  regex.lastIndex // 0
  regex.test(str) // true
  regex.lastIndex // 2

  regex.test(str) // false
  regex.lastIndex // 0
  regex.lastIndex = 3
  regex.test(str) // true
  ```

### 静态属性

* **\[@@species]：** 访问器属性返回 RegExp 的构造器。
* **lastIndex：** 是正则表达式的一个可读可写的整型属性，用来指定下一次匹配的起始索引，默认值为 0。

  只有正则表达式使用了表示全局检索的 "g" 或者粘性检索的 "y" 标志时，该属性才会起作用。

  * 如果 lastIndex 大于字符串的长度，则 test()、exec() 方法匹配失败。
  * 如果 lastIndex 等于或小于字符串的长度，则该正则表达式匹配从 lastIndex 位置开始。
  * 如果 test()、exec() 方法匹配成功，lastIndex 会被设置为紧随最近一次成功匹配的下一个位置。
  * 如果 test()、exec() 方法匹配失败，lastIndex 会被设置为 0。

  **注意：** 即使再次查找的字符串不是原查找字符串，lastIndex 值也不会被重置，依旧会从已记录的 lastIndex 值开始。

### 实例属性

* **flags：** 返回一个字符串，由当前正则表达式对象的标志组成。
* **global：** 返回布尔值，表明正则表达式是否使用了 "g" 标志，只读的属性。
* **ignoreCase：** 返回布尔值，表明正则表达式是否使用了 "i" 标志，只读的属性。
* **multiline：** 返回布尔值，表明正则表达式是否使用了 "m" 标志，只读的属性。
* **dotAll：** 返回布尔值，表明正则表达式是否使用了 "s" 修饰符，只读的属性。
* **unicode：** 返回布尔值，表明正则表达式是否使用了 "u" 标志，只读的属性。
* \*\*sticky：\*\*返回布尔值，表明正则表达式是否使用了 "y" 标志，只读的属性。
* **source：** 返回当前正则表达式对象的文本的字符串，该字符串不会包含正则字面量两边的斜杠以及任何的标志字符。

### 实例方法

* \*\*compile()（非标准）： \*\* 用于在脚本执行过程中（重新）编译正则表达式。可使用 RegExp 构造函数来得到相同效果。
* \*\*exec()： \*\* 返回一个结果数组或 null，用于在一个指定字符串中执行一个搜索匹配。如果匹配成功，则返回一个数组，并更新正则表达式对象的 lastIndex 属性；如果匹配失败，则返回 null，并将 lastIndex 重置为 0。

  在设置了 `g` 或 `y` 标志位的情况下，RegExp 对象是有状态的。它会将上次成功匹配后的位置记录在 lastIndex 属性中。使用此特性，该方法可用来对单个字符串中的多次匹配结果进行逐条的遍历（包括捕获到的匹配）。
* \*\*test()： \*\* 返回布尔值，表示指定的字符串与当前正则表达式是否匹配。

  如果正则表达式设置了 `g` 标志，该方法的执行会改变正则表达式对象的 lastIndex 属性。连续的执行该方法，后续的执行将会从 lastIndex 处开始匹配字符串。
* \*\*[@@match](/knowledge/javascript-xi-lie/01-ji-chu-pian-04-zheng-ze-biao-da-shi.md)： \*\* 返回对字符串使用正则表达式的匹配项。

  该方法在 String.prototype.match() 的内部调用，以下两种是等效的：

  ```javascript
  'abc'.match(/a/);
  /a/[Symbol.match]('abc');
  // ['a', index: 0, input: 'abc', groups: undefined]
  ```

  该方法可用于 RegExp 的子类重写 `[@@match]()`方法，即修改 String.prototype.match() 方法的默认匹配行为。

  ```javascript
  class RegExp1 extends RegExp {
      [Symbol.match](str) {
          const result = RegExp.prototype[Symbol.match].call(this, str);
          if (result) {
              return '匹配';
          }
          return '不匹配';
      }
  }
  console.log('abcd'.match(new RegExp1('123'))); // 不匹配
  ```
* [**@@matchAll**](/knowledge/javascript-xi-lie/01-ji-chu-pian-04-zheng-ze-biao-da-shi.md)**： 返回对字符串使用正则表达式的所有**匹配项的数组。

  该方法在 String.prototype.matchAll() 的内部调用，可用于 RegExp 的子类重写 `[@@matchAll]` 方法。
* \*\*[@@replace](/knowledge/javascript-xi-lie/01-ji-chu-pian-04-zheng-ze-biao-da-shi.md)： \*\* 用指定的替换器为一个字符串替换符合正则模式的匹配项，并返回替换后的新字符串结果。用来替换的参数可以是一个字符串或是一个针对每次匹配的回调函数。

  如果匹配模式是 RegExp 对象，该方法在 String.prototype.replace() 的内部调用；如果不是，则不会调用。该方法可用于 RegExp 的子类重写 `[@@replace]()` 方法。
* \*\*[@@search](/knowledge/javascript-xi-lie/01-ji-chu-pian-04-zheng-ze-biao-da-shi.md)： \*\* 在指定字符串中搜索匹配项，并返回在字符串中找到字符索引。如果匹配成功，返回第一个匹配项在字符串中的位置索引；否则返回 -1。

  该方法在 String.prototype.search() 的内部调用，可用于 RegExp 的子类重写 `[@@search]()` 方法。
* \*\*[@@split](/knowledge/javascript-xi-lie/01-ji-chu-pian-04-zheng-ze-biao-da-shi.md)： \*\* 通过指定的匹配项，将字符串拆分为子字符串，并返回字符串形成的数组。

  该方法在 String.prototype.split() 的内部调用，可用于 RegExp 的子类重写 `[@@split]()` 方法。
* \*\*toString()： \*\* 返回一个表示该正则表达式的字符串。

## 特殊字符

一个正则表达式可以由简单字符和特殊字符的组合。

比如，`/ab+c/` 中，`abc` 是简单字符，也称字面量字符，意味着字符只表示它字面的含义；`+` 是特殊字符，也称元字符，它不仅仅是字面上的含义。

### 字符类

字符类可以区分各种字符，例如区分字母和数字。

* `.`：表示匹配除行终止符（\n、\r、\u2028、\u2029）之外的任何单个字符。在字符集内，`.` 失去了它的特殊意义，并与文字 `.` 匹配。

  **注意：** 对于码点大于 0xFFFF 的 Unicode 字符，`.` 字符不能正确匹配，会认为这是两个字符。

  **注意：** `m` 标志不会改变 `.` 的行为。因此，要跨多行匹配一个模式，可以使用字符集 `[^]` ——它将匹配任何字符，包括新行。

  **注意：** ES2018 添加了 `s` 标志，它允许 `.` 匹配行终止符。
* `\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`：匹配单个**非**空格字符，包括空格、制表符、换页符、换行符和其他 Unicode 空格，相当于 `[^ \f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]`。
* `\r`：匹配一个回车符（U+000D）。
* `\n`：匹配一个换行符（U+000A）。
* `\t`：匹配一个横向制表符（U+0009）。
* `\v`：匹配一个竖向制表符（U+000B）。
* `\f`：匹配一个换页符（U+000C）。
* `b`：匹配一个退格符（U+0008）。
* `\0`：匹配 NUL（U+0000）字符， 不要在这后面跟其它数字，因为这是一个八进制转义。NUL 字符是 Unicode 中的空字符（U+0000），是标准印刷字符集的一部分。
* `\cX`：匹配一个控制字符（如，`\b`、`\n`、`\r`），X 是 \[A-Z] 中的任意一个字母（对应码点 U+0001\~U+001F）。
* `\xhh`：匹配一个以两位十六进制数（\x00-\xFF）表示的字符。
* `\uhhhh`：匹配一个以四位十六进制数（\u0000-\uFFFF）表示的 Unicode 字符。
* `\u{h...}`：匹配一个以 1\~5 位十六进制数表示的 Unicode 字符（仅适用于设置了 `u` 标志的正则表达式 ）。
* `\`：转义符，用于将简单字符转为特殊字符，如，`\n`、`\r`、`\b` 等；或者将特殊字符转为简单字符，如，`\*`、`\?`、`\+`、`\\` 等。

  **注意：** 如果使用 RegExp 构造函数生成 RegExp 对象，参数字符串中的转义符需要使用两个斜杠（`\\`），因为字符串内部会先转义一次。

```javascript
'\n'.charCodeAt().toString(16) // 'a'
'\r'.charCodeAt().toString(16) // 'd'
/\u000D/.exec('\r') // ['\r', index: 0, input: '\r', groups: undefined]
/\cM/.exec('\n\r')  // ['\r', index: 0, input: '\r\n', groups: undefined]

/([\u0000-\u0019\u0021-\uFFFF])+/gu.exec("Приключения Алисы в Стране чудес")
// 除空格外的所有 unicode 字符
```

### 量词

量词表示要匹配的字符或表达式的数量。

* `*`：表示匹配 0 次或 0 次以上，等价于 {0,}。
* `+`：表示至少匹配 1 次，等价于 {1,}。
* `?`：表示匹配 0 或 1 次，等价于 {0,1}。如果立即在任何量词 `*、+、?、{}` 之后使用，则表示量词是非贪婪的（匹配最小次数），而不是默认的贪婪的（匹配最大次数）。
* `{n}`：表示匹配 n 次。
* `{n,}`：表示**至少**匹配 n 次。
* `{n, m}`：表示**至少**匹配 n 次且最多匹配 m 次。
* `*?、+?、??、{n}?、{n,}?、{n,m}?`：默认情况下，`* +` 这些量词是 “贪婪的”，即，它们试图匹配尽可能多的字符串。量词后面的 `?` 字符使量词 “非贪婪”，即，一旦找到匹配就会停止。

```javascript
/a*/.exec('bc')        // ['', index: 0, input: 'bc', groups: undefined]
/a*/.exec('aaabc')     // ['aaa', index: 0, input: 'aaabc', groups: undefined]
/a+/.exec('aaabc')     // ['aaa', index: 0, input: 'aaabc', groups: undefined]
/a+/.exec('bc')        // null
/a?/.exec('aaabc')     // ['a', index: 0, input: 'aaabc', groups: undefined]
/a{1}/.exec('aaabc')   // ['a', index: 0, input: 'aaabc', groups: undefined]
/a{1,}/.exec('aaabc')  // ['aaa', index: 0, input: 'aaabc', groups: undefined]
/a{1,2}/.exec('aaabc') // ['aa', index: 0, input: 'aaabc', groups: undefined]
/a{1,3}/.exec('aaabc') // ['aaa', index: 0, input: 'aaabc', groups: undefined]
   
/a*?/.exec('aaabc')     // ['', index: 0, input: 'aaabc', groups: undefined]
/a+?/.exec('aaabc')     // ['a', index: 0, input: 'aaabc', groups: undefined]
/a??/.exec('aaabc')     // ['', index: 0, input: 'aaabc', groups: undefined]
/a{1,}?/.exec('aaabc')  // ['a', index: 0, input: 'aaabc', groups: undefined]
/a{1,3}?/.exec('aaabc') // ['a', index: 0, input: 'aaabc', groups: undefined]
```

### 组和范围

表示表达式字符的组和范围。

* `|`： 选择符，表示匹配其中任意一个。**注意：** `|` 会包括它前后的多个字符。
* `[]`： 字符集，表示匹配任何一个包含在中括号内的字符。如，\[xyz] 表示匹配 `xyz` 中的任意一个字符；\[x-z] 与\[xyz] 等同，其中 `-` 是连字符，用来指定字符范围。
* `^`： 表示一个否定的字符集，即匹配任何一个没有包含在中括号内的字符。如，`[^xyz]` 表示匹配除 `xyz` 之外的任意一个字符，同样也可以使用连字符`[^x-z]` 。

  另外，`^` 也可以表示行的起始位置。
* `()`： **捕获组**，也称捕获括号，匹配括号内的内容，并在匹配结果中返回。

  **注意：** 正则表达式可以有多个捕获组。

  **注意：** 正则表达式内部，还可以用 `\n` 引用括号匹配的内容，n 是从 1 开始的自然数，表示对应顺序的第 n 个捕获组。如，`/(a)b(c)(\1)(\2)(\3)/` 等同于 `/(a)b(c)(a)(c)(a)/`。
* `\n`： 表示正则表达式中第 n 个分组（即，括号）内的文本。
* `(?<Name>x)`： **具名捕获组**，匹配 x 并将其存储在返回的匹配项的 groups 属性中，该属性位于 `<Name>` 指定的名称下。**注意：** 并不是所有的浏览器都支持这个功能;
* `(?:x)`： **非捕获组**，也称非捕获括号，匹配括号内的内容，但**不**在匹配结果中返回。

```javascript
/ab|cd/.test('b')   // false。匹配的是 ab 或 cd，而不是 b 或 c。
/[x-z]/.test('-')   // false
/[x-]/.test('-')    // true

/abcd+/.exec('abcdabcd')
// ['abcd', index: 0, input: 'abcdabcd', groups: undefined]
/(abcd)+/.exec('abcdabcd')
// ['abcdabcd', 'abcd', index: 0, input: 'abcdabcd', groups: undefined]

/(a)b(c)(\1)(\2)(\3)/.exec('abcaca')
/(a)b(c)(a)(c)(a)/.exec('abcaca')
// ['abcaca', 'a', 'c', 'a', 'c', 'a', index: 0, input: 'abcaca', groups: undefined]

const result1 = /(?<first>a)b(c)(a)(c)(a)/.exec('abcaca')
result1.groups.first // 'a'

/(a)b(c)/.exec('abcaca')
// ['abc', 'a', 'c', index: 0, input: 'abcaca', groups: undefined]
/(?:a)b(?:c)/.exec('abcaca')
// ['abc', index: 0, input: 'abcaca', groups: undefined]
```

### 断言

断言的组成之一是边界。对于文本、词或模式，边界可以用来表明它们的起始或终止部分（如向前断言，向后断言以及条件表达式）。

* `^`： 匹配输入的**开头**。如果正则表达式有 `m` 标志，则在换行符**后**也能立即匹配。

  **注意：** `^` 出现在中括号开头时的含义与此不同。
* `$`： 匹配输入的**结束**。如果正则表达式有 `m` 标志，则在换行符**前**也能立即匹配。
* `\b`： 匹配一个词的边界。一个词的边界就是一个词的前面或者后面不是另一个“字” 字符。

  **注意：** 匹配的单词边界不包括在匹配结果中。

  **注意：** `/\w\b\w/` 将永远不会匹配任何东西，因为在一个单词中间的字符永远也不可能同时满足没有 “字” 字符跟随和有“字”字符跟随两种情况。

  > **备注：** JavaScript 的正则表达式引擎将 **特定的字符集** 定义为 “字” 字符。不在该集合中的任何字符都被认为是一个断词。"字" 字符集相当有限，仅包括：大写和小写的字母、十进制数字和下划线字符，即\[a-zA-z0-9\_]，其他字符被视为断词。
* `\B`：匹配一个非单词边界。匹配情况有：字符串第一个或最后一个字符为非 “字” 字符（即字符串的开头和结尾边界）、两个单词字符之间、两个非单词字符之间、空字符串。
* **注意：** 字符串的开头和结尾被视为非单词。

  **注意：** 匹配的单词边界不包括在匹配结果中。
* `x(?=y)`： **向前断言，** 匹配 x 仅仅当 x 后面跟着 y，但匹配结果不包括 y。
* `x(?!y)`： **向前否定断言**， 匹配 x 仅仅当 x 后面**不**跟着 y，，但匹配结果不包括 y。
* `(?<=y)x`： **向后断言**，匹配 x 仅当 x 前面是 y，但匹配结果不包括 y。
* `(?<!y)x`： **向后否定断言**，匹配 x 仅当 x 前面**不**是 y，但匹配结果不包括 y。

```javascript
/^a/.test('bacd\naefg')  // false
/^a/.test('abcd\naefg')  // true
/^a/m.test('bacd\naefg') // true

/\bm/.test('moon')   // true
/\bm/.test(',moon')  // true
/n\b/.test('moon')   // true
/\bn/.test('moon')   // false
/\bn/.test('moo n')  // true
/\w\b\w/.test('ab')  // false
/\w\b\w/.test('a b') // false

/\Bm/.test('moon')   // false
/\Bn/.test('moo n')  // false
/\B /.test(' ')      // true

/a(?=c)/.exec('abac')  // ['a', index: 2, input: 'abac', groups: undefined]
/a(?!c)/.exec('abac')  // ['a', index: 0, input: 'abac', groups: undefined]
/(?<=b)a/.exec('abac') // ['a', index: 2, input: 'abac', groups: undefined]\
/(?<!b)a/.exec('abac') // ['a', index: 0, input: 'abac', groups: undefined]
```

## 使用RegExp的String实例方法

* **match(regexp)：** 返回一个字符串匹配正则表达式的结果，匹配失败则返回 null。
* **matchAll(regexp)：** 返回一个包含所有匹配正则表达式的结果及分组捕获组的**迭代器**，匹配失败则返回一个一个空的可迭代对象。**注意：** RegExp 必须是设置了 `g` 标志，否则会抛出异常 TypeError。
* **split(regexp|substr, limit)：** 返回一个用指定分隔符将字符串分割成子字符串的数组。
* **search(regexp)：** 返回第一个匹配项的位置，如果未找到，则返回 -1。
* **replace(str|regexp, str|func)：** 返回一个由替换值替换匹配项后的新字符串，匹配项可以是一个字符串或者一个正则表达式，替换值可以是一个字符串或者一个每次匹配都要调用的回调函数。
* **replaceAll(str|regexp, str|func)：** 返回一个所有满足匹配项的部分都已被替换值替换的新字符串。与 replace() 类似，区别是它会替换字符串中所有满足匹配的字符。**注意：** 当使用一个正则表达式时，必须设置`g` 标志。

```javascript
'abcd1234,./%'.match(/[\w]/)
// ['a', index: 0, input: 'abcd1234,./%', groups: undefined]
'abcd1234,./%'.match(/[\w]/g)
// (8) ['a', 'b', 'c', 'd', '1', '2', '3', '4']

'abcd1234,./%'.matchAll(/\w/g)      // RegExpStringIterator {}
'abcd1234,./%'.split(/\d+/)         // ['abcd', ',./%']
'abcd1234,./%'.search(/\d+/)        // 4
'abcd1234,./%'.replace(/\d+/, '——') // 'abcd——,./%'
```

## 常用正则表达式

```javascript
// 十六进制颜色值
/#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/

// 日期: yyyy-mm-dd
/^[0-9]{4}-(0[1-9]|1[0-2])-([012][0-9]|3[01])$/

// 带1-2位小数
/^(-)?\d+(.\d{1,2})?$/

// 汉字
/^[\u4e00-\u9fa5]{0,}$/  
```

## 相关问题

### y 标志和 g 标志的差别

如果正则表达式有粘性 `y` 标志，下一次匹配一定在 lastIndex 位置开始；如果正则表达式有全局 `g` 标志，下一次匹配可能在 lastIndex 位置开始，也可能在这个位置的后面开始。

```javascript
const regExp1 = /\d/y;
while (r = regExp1.exec("123 456")) {
    console.log(r, regExp1.lastIndex)
}
// ['1', index: 0, input: '123 456', groups: undefined] 1
// ['2', index: 1, input: '123 456', groups: undefined] 2
// ['3', index: 2, input: '123 456', groups: undefined] 3

const regExp2 = /\d/g;
while (r = regExp2.exec("123 456")) {
    console.log(r, regExp2.lastIndex)
}
// ['1', index: 0, input: '123 456', groups: undefined] 1
// ['2', index: 1, input: '123 456', groups: undefined] 2
// ['3', index: 2, input: '123 456', groups: undefined] 3
// ['4', index: 4, input: '123 456', groups: undefined] 5
// ['5', index: 5, input: '123 456', groups: undefined] 6
// ['6', index: 6, input: '123 456', groups: undefined] 7
```

### 使用正则表达式和 Unicode 字符

正常情况下，`\w` 或 `\W` 只会匹配基本的 ASCII 字符，如 a~~z、A~~Z、0\~9 和 \_。

为了匹配其他语言中的字符，如西里尔（Cyrillic）或 希伯来语（Hebrew），要使用 \uhhhh，hhhh 表示以十六进制表示的字符的 Unicode 值。

```javascript
// 拉丁字母
'Ⱡ'.charCodeAt().toString(16) // '2c60'
'Ᵽ'.charCodeAt().toString(16) // '2c63'
'Ⱪ'.charCodeAt().toString(16) // '2c69'

const regExp = /[\u2c60|\u2c63|\u2c69]/gu
regExp.exec('aⱠbⱣcⱩ') // ['Ⱡ', index: 1, input: 'aⱠbⱣcⱩ', groups: undefined]
regExp.exec('aⱠbⱣcⱩ') // ['Ᵽ', index: 3, input: 'aⱠbⱣcⱩ', groups: undefined]
regExp.exec('aⱠbⱣcⱩ') // ['Ⱪ', index: 5, input: 'aⱠbⱣcⱩ', groups: undefined]

/[\u{2c60}-\u{2c77}]/u.exec('aⱠbⱣcⱩ')
// ['Ⱡ', index: 1, input: 'aⱠbⱣcⱩ', groups: undefined]
/[\u{2c60}-\u{2c77}]/.exec('aⱠbⱣcⱩ')
// Uncaught SyntaxError: Invalid regular expression: /[\u{2c60}-\u{2c77}]/: Range out of order in character class
```

## 参考资料

[MDN - 正则表达式](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions)

[MDN - RegExp（正则表达式）](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp)

[Unicode 字符列表](https://zh.m.wikipedia.org/zh/Unicode%E5%AD%97%E7%AC%A6%E5%88%97%E8%A1%A8)

[常用JS正则大全（2019年11月12日更新）](https://juejin.cn/post/6844903886004682765)
