模式(Patterns)和修饰符(flags)
正则表达式是搜索和替换字符串的一种强大方式。
在 JavaScript 中,正则表达式通过内置的“RegExp”类的对象来实现,并与字符串集成。
正则表达式
正则表达式(可叫作“regexp”或者“reg”)包含 模式 和可选的 修饰符。
创建一个正则表达式对象有两种语法。
较长一点的语法:
regexp = new RegExp("pattern", "flags");
较短一点的语法,使用斜杠 “/“:
regexp = /pattern/; // 没有修饰符
regexp = /pattern/gmi; // 伴随修饰符 g、m 和 i(后面会讲到)
斜杠 “/“ 会告诉 JavaScript 我们正在创建一个正则表达式。它的作用类似于字符串的引号。
用法
如果要在字符串中进行搜索,可以使用 search 方法。
let str = "I love JavaScript!"; // 将在这里搜索
let regexp = /love/;
alert( str.search(regexp) ); // 2
str.search 方法会查找模式 /love/,然后返回匹配项在字符串中的位置。我们可以猜到,/love/ 是最简单的模式。它所做的就是简单的子字符串的查找。
上面的代码等同于:
let str = "I love JavaScript!"; // 将在这里搜索
let substr = 'love';
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
- 一个正则表达式包含模式和可选修饰符: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 的支持。
这意味着两件事:
- 4 个字节长的字符被以正确的方式处理:被看成单个的字符,而不是 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 以使其正常工作。
例如,让我们在字符串 𝒳 中查找 [𝒳𝒴]:
没有标志 u 的代理对被视为两个字符.alert( '𝒳'.match(/[𝒳𝒴]/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。
接下来的例子将会查找一个五位数的数字:
某个范围的位数:{3,5}alert( "I'm 12345 years old".match(/\d{5}/) ); // "12345"
我们可以将限制范围的数字放入括号中,来查找位数为 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 的情况。