模式(Patterns)和修饰符(flags)

正则表达式是搜索和替换字符串的一种强大方式。
在 JavaScript 中,正则表达式通过内置的“RegExp”类的对象来实现,并与字符串集成。

正则表达式

正则表达式(可叫作“regexp”或者“reg”)包含 模式 和可选的 修饰符
创建一个正则表达式对象有两种语法。
较长一点的语法:

  1. regexp = new RegExp("pattern", "flags");

较短一点的语法,使用斜杠 “/“:

  1. regexp = /pattern/; // 没有修饰符
  2. regexp = /pattern/gmi; // 伴随修饰符 g、m 和 i(后面会讲到)

斜杠 “/“ 会告诉 JavaScript 我们正在创建一个正则表达式。它的作用类似于字符串的引号。

用法

如果要在字符串中进行搜索,可以使用 search 方法。

  1. let str = "I love JavaScript!"; // 将在这里搜索
  2. let regexp = /love/;
  3. alert( str.search(regexp) ); // 2

str.search 方法会查找模式 /love/,然后返回匹配项在字符串中的位置。我们可以猜到,/love/ 是最简单的模式。它所做的就是简单的子字符串的查找。
上面的代码等同于:

  1. let str = "I love JavaScript!"; // 将在这里搜索
  2. let substr = 'love';
  3. alert( str.search(substr) ); // 2

修饰符 i g m u y

正则表达式的修饰符可能会影响搜索结果。
在 JavaScript 中,有 5 个修饰符:
i
使用此修饰符后,搜索时不区分大小写: A 和 a 没有区别(具体看下面的例子)。
g
使用此修饰符后,搜索时会查找所有的匹配项,而不只是第一个(在下一章会讲到)。
m
多行模式(详见章节 Flag “m” — 多行模式)。
u
开启完整的 unicode 支持。该修饰符能够修正对于代理对的处理。更详细的内容见章节 Unicode:修饰符 “u” 和 class \p{…}。
y
粘滞模式(详见 下一章节)

“i”修饰符

let str = "I love JavaScript!";

alert( str.search(/LOVE/) ); // -1(没找到)
alert( str.search(/LOVE/i) ); // 2
  1. 第一个搜索返回的是 -1(也就是没找到),因为搜索默认是区分大小写的。
  2. 使用修饰符 /LOVE/i,在字符串的第 2 个位置上搜索到了 love。

    总结

  • 一个正则表达式包含模式和可选修饰符:g、i、m、u、y。
  • 如果不使用我们在后面将要学到的修饰符和特殊标志,正则表达式的搜索就等同于子字符串查找。
  • str.search(regexp) 方法返回的是找到的匹配项的索引位置,如果没找到则返回 -1。

    字符类

    考虑一个实际的任务 – 我们有一个电话号码,例如 “+7(903)-123-45-67”,我们需要将其转换为纯数字:79031234567。
    为此,我们可以查找并删除所有非数字的内容。字符类可以帮助解决这个问题。
    字符类(Character classes) 是一个特殊的符号,匹配特定集中的任何符号。
    首先,让我们探索“数字”类。它写为 \d,对应于“任何一个数字”
    例如,让我们找到电话号码的第一个数字: ```javascript let str = “+7(903)-123-45-67”;

let regexp = /\d/;

alert( str.match(regexp) ); // 7

如果没有标志 g,则正则表达式仅查找第一个匹配项,即第一个数字 \d。<br />让我们**添加 g标志来查找所有数字:**
```javascript
let str = "+7(903)-123-45-67";

let regexp = /\d/g;

alert( str.match(regexp) ); // array of matches: 7,9,0,3,1,2,3,4,5,6,7

// let's make the digits-only phone number of them:
alert( str.match(regexp).join('') ); // 79031234567

这是数字的字符类。还有其他字符类。
最常用的是:
\d(“d” 来自 “digit”)
数字:从 0 到 9 的字符。
\s(“s” 来自 “space”)
空格符号:包括空格,制表符 \t,换行符 \n 和其他少数稀有字符,例如 \v,\f 和 \r。
\w(“w” 来自 “word”)
“单字”字符:拉丁字母或数字或下划线 _。非拉丁字母(如西里尔字母或印地文)不属于 \w。
例如,\d\s\w表示“数字”,后跟“空格字符”,后跟“单字字符”,例如 1 a。
正则表达式可能同时包含常规符号和字符类。
例如,CSS\d 匹配字符串 CSS 与后面的数字:regexp = /CSS\d/
我们还可以使用许多字符类:alert(“I love HTML5!”.match(/\s\w\w\w\w\d/));// ‘ HTML5’

反向类

对于每个字符类,都有一个“反向类”,用相同的字母表示,但要以大写书写形式。
“反向”表示它与所有其他字符匹配
\D
非数字:除 \d 以外的任何字符,例如字母。
\S
非空格符号:除 \s 以外的任何字符,例如字母。
\W
非单字字符:除 \w 以外的任何字符,例如非拉丁字母或空格。

点(.)是匹配“任何字符”

点 . 是一种特殊字符类,它与 “除换行符之外的任何字符” 匹配。
请注意,点表示“任何字符”,而不是“缺少字符”。必须有一个与之匹配的字符:

带有“s”标志时点字符类严格匹配任何字符

默认情况下,点与换行符 \n 不匹配。

alert( "A\nB".match(/A.B/) ); // null (no match)

模式 [\s\S] 从字面上说:“空格字符或非空格字符”。换句话说,“任何东西”。我们可以使用另一对互补的类,例如 [\d\D]。甚至是 [^] —— 意思是匹配任何字符,除了什么都没有。

alert( "A\nB".match(/A[\s\S]B/) ); // A\nB (match!)

空格是一个字符。与其他字符同等重要。

// 让我们尝试查找由连字符(-)分隔的数字:
alert( "1 - 5".match(/\d-\d/) ); // null, no match!


// 让我们修复一下,在正则表达式中添加空格:\ d-\ d`:

alert( "1 - 5".match(/\d - \d/) ); // 1 - 5, now it works
// or we can use \s class:
alert( "1 - 5".match(/\d\s-\s\d/) ); // 1 - 5, also works

总结

存在以下字符类:

  • \d —— 数字。
  • \D —— 非数字。
  • \s —— 空格符号,制表符,换行符。
  • \S —— 除了 \s 。
  • \w —— 拉丁字母,数字,下划线 ‘_’。
  • \W —— 除了 \w。
  • . —— 任何带有 ‘s’ 标志的字符,否则为除换行符 \n之外的任何字符。

    Unicode:修饰符 “u” 和 class \p{…}

    JavaScript 使用 Unicode 编码 (Unicode encoding)对字符串进行编码。

    Unicode 属性(Unicode properties)\p{…}

    总结

    修饰符 u 在正则表达式中提供对 Unicode 的支持。
    这意味着两件事:
  1. 4 个字节长的字符被以正确的方式处理:被看成单个的字符,而不是 2 个 2 字节长的字符。
  2. Unicode 属性可以被用于查找中 \p{…}。

    锚点(Anchors):字符串开始 ^ 和末尾 $

    插入符号 ^ 和美元符号 $ 在正则表达式中具有特殊的意义。它们被称为“锚点”。
    插入符号 ^ 匹配文本开头,而美元符号 $ - 则匹配文本末尾

    测试完全匹配

    这两个锚点 ^…$ 放在一起常常被用于测试一个字符串是否完全匹配一个模式。比如,测试用户的输入是否符合正确的格式。

    Flag “m” — 多行模式

    通过 flag /…/m 可以开启多行模式。
    这仅仅会影响 ^ 和 $ 锚符的行为。
    在多行模式下,它们不仅仅匹配文本的开始与结束,还匹配每一行的开始与结束。

    行的开头 ^

    在这个有多行文本的例子中,正则表达式 /^\d+/gm 将匹配每一行的开头数字: ``javascript let str =1st place: Winnie 2nd place: Piglet 33rd place: Eeyore`;

alert( str.match(/^\d+/gm) ); // 1, 2, 33

<a name="Sl9aA"></a>
## 行的结尾 $
<a name="gmJsY"></a>
## 锚符 ^$ 对比 \n
要寻找新的一行的话,我们不仅可以使用锚符 ^ 和 $,也可以使用换行符 \n。它和锚符 ^ 和 $ 的第一个不同点是它不像锚符那样,它会“消耗”掉 \n 并且将其(\n)加入到匹配结果中。
<a name="PE19E"></a>
# 词边界:\b
词边界 \b 是一种检查,就像 ^ 和 $ 一样。<br />当正则表达式引擎(实现搜索正则表达式的程序模块)遇到 \b 时,它会检查字符串中的位置是否是词边界。<br />例如,可以在 Hello, Java! 中找到匹配 \bJava\b 的单词,其中 Java 是一个独立的单词,而在 Hello, JavaScript! 中则不行。
```javascript
alert( "Hello, Java!".match(/\bHello\b/) ); // Hello
alert( "Hello, Java!".match(/\bJava\b/) );  // Java
alert( "Hello, Java!".match(/\bHell\b/) );  // null (no match)
alert( "Hello, Java!".match(/\bJava!\b/) ); // null (no match)

\b 既可以用于单词,也可以用于数字。
例如,模式 \b\d\d\b 查找独立的两位数。换句话说,它查找的是两位数,其周围是与 \w 不同的字符,例如空格或标点符号(或文本开头/结尾)。

alert( "1 23 456 78".match(/\b\d\d\b/g) ); // 23,78
alert( "12,34,56".match(/\b\d\d\b/g) ); // 12,34,56

转义,特殊字符

一个反斜杠 “\” 是用来表示匹配字符类的。所以它是一个特殊字符。
这里是包含所有特殊字符的列表:[ \ ^ $ . | ? * + ( )。

转义

如果要把特殊字符作为常规字符来使用,只需要在它前面加个反斜杠。

一个斜杠

下面是查询斜杠 ‘/‘ 的表达式:

alert( "/".match(/\//) ); // '/'

从另一个方面看,如果使用另一种 new RegExp 方式就不需要转义斜杠:

alert( "/".match(new RegExp("/")) ); // '/'

使用 new RegExp 创建正则实例

调用 new RegExp 会获得一个没有反斜杠的字符串。

let reg = new RegExp("\d\.\d");

alert( "Chapter 5.1".match(reg) ); // null

alert("\d\.\d"); // d.d

如果要修复这个问题,我们需要双斜杠,因为引用会把 \ 变为 \:

let regStr = "\\d\\.\\d";
alert(regStr); // \d\.\d (correct now)

let regexp = new RegExp(regStr);

alert( "Chapter 5.1".match(regexp) ); // 5.1

集合和范围 […]

在方括号 […] 中的几个字符或者字符类意味着“搜索给定的字符中的任意一个”。

集合

比如说,[eao] 意味着查找在 3 个字符 ‘a’、’e’ 或者 `‘o’ 中的任意一个。
这被叫做一个集合。集合可以在正则表达式中和其它常规字符一起使用。

// 查找 [t 或者 m],然后再匹配 “op”
alert( "Mop top".match(/[tm]op/gi) ); // "Mop", "top"

范围

方括号也可以包含字符范围
比如说,[a-z] 会匹配从 a 到 z 范围内的字母,[0-5] 表示从 0 到 5 的数字。

alert( "Exception 0xAF".match(/x[0-9A-F][0-9A-F]/g) ); // xAF

[0-9A-F] 表示两个范围:它搜索一个字符,满足数字 0 到 9 或字母 A 到 F。

示例:多语言 \w

?

排除范围

除了普通的范围匹配,还有类似 [^…] 的“排除”范围匹配。
它们通过在匹配查询的开头添加插入符号 ^ 来表示,它会匹配所有除了给定的字符之外的任意字符。
比如说:

  • [^aeyo] —— 匹配任何除了 ‘a’、’e’、’y’ 或者 ‘o’ 之外的字符。
  • [^0-9] —— 匹配任何除了数字之外的字符,也可以使用 \D 来表示。
  • [^\s] —— 匹配任何非空字符,也可以使用 \S 来表示。

    在 […] 中不转义

    除了在方括号中有特殊含义的字符外,其它所有特殊字符都是允许不添加反斜杠的。

    范围和标志“u”

    如果集合中有代理对(surrogate pairs),则需要标志 u 以使其正常工作。
    例如,让我们在字符串 𝒳 中查找 [𝒳𝒴]:
    alert( '𝒳'.match(/[𝒳𝒴]/u) ); // 𝒳
    
    没有标志 u 的代理对被视为两个字符.

    写一个正则表达式来找到时间

    时间可以通过 hours:minutes 或者 hours-minutes 格式来表示。小时和分钟都有两个数字:09:00 或者 21-30。
    let reg = /\d\d[-:]\d\d/g;
    alert( "Breakfast at 09:00. Dinner at 21-30".match(reg) ); // 09:00, 21-30
    

    量词 +,*,?{n}

    数量 {n}

    最明显的量词便是一对引号间的数字:{n}。
    确切的位数:{5}
    \d{5} 表示 5 位的数字,如同 \d\d\d\d\d。
    接下来的例子将会查找一个五位数的数字:
    alert( "I'm 12345 years old".match(/\d{5}/) ); //  "12345"
    
    某个范围的位数:{3,5}
    我们可以将限制范围的数字放入括号中,来查找位数为 3 至 5 位的数字:\d{3,5}
    我们可以省略上限。那么正则表达式 \d{3,} 就会查找位数大于或等于 3 的数字

    缩写

    大多数常用的量词都可以有缩写:
    +
    代表“一个或多个”,相当于 {1,}。
    例如,\d+ 用来查找所有数字: ```javascript let str = “+7(903)-123-45-67”;

alert( str.match(/\d+/g) ); // 7,903,123,45,67

**?**<br />代表“零个或一个”,相当于 {0,1}。换句话说,它使得符号变得可选。<br />例如,模式 ou?r 查找 o,后跟零个或一个 u,然后是 r。<br />*****<br />代表着“零个或多个”,相当于 {0,}。也就是说,这个字符可以多次出现或不出现。
<a name="VYkCq"></a>
## 更多示例
**正则表达式“浮点数”(带浮点的数字):\d+\.\d+**
```javascript
alert( "0 1 12.345 7890".match(/\d+\.\d+/g) ); // 12.345

正则表达式“打开没有属性的 HTML 标记”,比如

:/<[a-z]+>/i

alert( "<body> ... </body>".match(/<[a-z]+>/gi) ); // <body>

正则表达式“打开没有属性的HTML标记”(改进版):/<[a-z][a-z0-9]*>/i

alert( "<h1>Hi!</h1>".match(/<[a-z][a-z0-9]*>/gi) ); // <h1>

正则表达式“打开没有属性的HTML标记”:/<\/?[a-z][a-z0-9]*>/i

alert( "<h1>Hi!</h1>".match(/<\/?[a-z][a-z0-9]*>/gi) ); // <h1>, </h1>

贪婪量词和惰性量词

捕获组

模式中的反向引用:\N 和 \k

选择(OR)|

前瞻断言与后瞻断言

前瞻断言

语法为:x(?=y),它表示 “匹配 x, 仅在后面是 y 的情况””
那么对于一个后面跟着 € 的整数金额,它的正则表达式应该为:\d+(?=€)。

let str = "1 turkey costs 30€";

alert( str.match(/\d+(?=€)/) ); // 30 (正确地跳过了单个的数字 1)

前瞻否定断言
语法为:x(?!y),意思是 “查找 x, 但是仅在不被 y 跟随的情况下匹配成功”。

let str = "2 turkeys cost 60€";

alert( str.match(/\d+(?!€)/) ); // 2(正确地跳过了价格)

后瞻断言

它只允许匹配前面有特定字符串的模式。
语法为:

  • 后瞻肯定断言:(?<=y)x, 匹配 x, 仅在前面是 y 的情况。
  • 后瞻否定断言:(?<!y)x, 匹配 x, 仅在前面不是 y 的情况。