模式(Patterns)和修饰符(flags)
正则表达式是搜索和替换字符串的一种强大方式。
在 JavaScript 中,正则表达式通过内置的“RegExp”类的对象来实现,并与字符串集成。
请注意,在各编程语言之间,正则表达式是有所不同的。在本教程中,我们只专注于 JavaScript。当然,它们有很多共同点,但在 Perl、Ruby 和 PHP 等语言下会有所不同。
正则表达式
正则表达式(可叫作“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
所以搜索 /love/ 与搜索 "love" 是等价的。
但这只是暂时的。很快我们就会接触更复杂的正则表达式,其搜索功能将更强大。
配色
本文中的配色方案如下:
- regexp –
red - string(我们要搜索的)--
blue - result –
green
什么时候使用 new RegExp?
通常我们使用的都是简短语法 /.../。但是它不接受任何变量插入,所以我们必须在写代码的时候就知道确切的 regexp。
另一方面,new RegExp 允许从字符串中动态地构造模式。
所以我们可以找出需要搜索的字段,然后根据搜索字段创建 new RegExp:
let search = prompt("What you want to search?", "love");
let regexp = new RegExp(search);
// 找到用户想要的任何东西
alert( "I love JavaScript".search(regexp));
修饰符
正则表达式的修饰符可能会影响搜索结果。
在 JavaScript 中,有 5 个修饰符:
-
i使用此修饰符后,搜索时不区分大小写:
A和a没有区别(具体看下面的例子)。 -
g使用此修饰符后,搜索时会查找所有的匹配项,而不只是第一个(在下一章会讲到)。
-
m多行模式(详见章节 文章 "regexp-multiline" 未找到)。
-
u开启完整的 unicode 支持。该修饰符能够修正对于代理对的处理。更详细的内容见章节 Unicode:修饰符 “u” 和 class \p{...}。
-
y粘滞模式(详见 下一章节)
“i”修饰符
最简单的修饰符就是 i 了。
示例代码如下:
let str = "I love JavaScript!";
alert( str.search(/LOVE/) ); // -1(没找到)
alert( str.search(/LOVE/i) ); // 2
- 第一个搜索返回的是
-1(也就是没找到),因为搜索默认是区分大小写的。 - 使用修饰符
/LOVE/i,在字符串的第 2 个位置上搜索到了love。
相比与简单的子字符串查找,i 修饰符已经让正则表达式变得更加强大了。但是这还不够。我们会在下一章节讲述其它修饰符和特性。
总结
- 一个正则表达式包含模式和可选修饰符:
g、i、m、u、y。 - 如果不使用我们在后面将要学到的修饰符和特殊标志,正则表达式的搜索就等同于子字符串查找。
str.search(regexp)方法返回的是找到的匹配项的索引位置,如果没找到则返回-1。
字符类
考虑一个实际的任务 – 我们有一个电话号码,例如 "+7(903)-123-45-67",我们需要将其转换为纯数字:79035419441。
为此,我们可以查找并删除所有非数字的内容。字符类可以帮助解决这个问题。
字符类(Character classes) 是一个特殊的符号,匹配特定集中的任何符号。
首先,让我们探索“数字”类。它写为 \d,对应于“任何一个数字”。
例如,让我们找到电话号码的第一个数字:
let str = "+7(903)-123-45-67";
let regexp = /\d/;
alert( str.match(regexp) ); // 7
如果没有标志 g,则正则表达式仅查找第一个匹配项,即第一个数字 \d。
让我们添加 g标志来查找所有数字:
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('') ); // 79035419441
这是数字的字符类。还有其他字符类。
最常用的是:
-
\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 与后面的数字:
let str = "Is there CSS4?";
let regexp = /CSS\d/
alert( str.match(regexp) ); // CSS4
我们还可以使用许多字符类:
alert( "I love HTML5!".match(/\s\w\w\w\w\d/) ); // ' HTML5'
匹配项(每个正则表达式字符类都有对应的结果字符):
反向类
对于每个字符类,都有一个“反向类”,用相同的字母表示,但要以大写书写形式。
“反向”表示它与所有其他字符匹配,例如:
-
\D非数字:除
\d以外的任何字符,例如字母。 -
\S非空格符号:除
\s以外的任何字符,例如字母。 -
\W非单字字符:除
\w以外的任何字符,例如非拉丁字母或空格。
在这一章的开头,我们看到了如何从 +7(903)-123-45-67 这样的字符串中创建一个只包含数字的电话号码: 找到所有的数字并将它们连接起来。
let str = "+7(903)-123-45-67";
alert( str.match(/\d/g).join('') ); // 79031234567
另一种快捷的替代方法是查找非数字 \D 并将其从字符串中删除:
let str = "+7(903)-123-45-67";
alert( str.replace(/\D/g, "") ); // 79031234567
点(.)是匹配“任何字符”
点 . 是一种特殊字符类,它与 “除换行符之外的任何字符” 匹配。
例如:
alert( "Z".match(/./) ); // Z
或在正则表达式中间:
let regexp = /CS.4/;
alert( "CSS4".match(regexp) ); // CSS4
alert( "CS-4".match(regexp) ); // CS-4
alert( "CS 4".match(regexp) ); // CS 4 (space is also a character)
请注意,点表示“任何字符”,而不是“缺少字符”。必须有一个与之匹配的字符:
alert( "CS4".match(/CS.4/) ); // null, no match because there's no character for the dot
带有“s”标志时点字符类严格匹配任何字符
默认情况下,点与换行符 \n 不匹配。
例如,正则表达式 A.B 匹配 A,然后匹配 B 和它们之间的任何字符,除了换行符\n:
alert( "A\nB".match(/A.B/) ); // null (no match)
在许多情况下,当我们希望用点来表示“任何字符”(包括换行符)时。
这就是标志 s 所做的。如果有一个正则表达式,则点 . 实际上匹配任何字符:
alert( "A\nB".match(/A.B/s) ); // A\nB (match!)
不支持 Firefox、IE、Edge
使用前可从 caniuse.com/#search=dot… 确认以获得最新的支持状态。在撰写本文时,它不包括 Firefox、IE、Edge。
幸运的是,有一种替代方法可以在任何地方使用。我们可以使用诸如 [\s\S] 之类的正则表达式来匹配“任何字符”。
alert( "A\nB".match(/A[\s\S]B/) ); // A\nB (match!)
模式 [\s\S] 从字面上说:“空格字符或非空格字符”。换句话说,“任何东西”。我们可以使用另一对互补的类,例如 [\d\D]。甚至是 [^] —— 意思是匹配任何字符,除了什么都没有。
如果我们希望两种“点”都使用相同的模式,也可以使用此技巧:实际的点 . 具有常规方式(“不包括换行符”)以及一种使用 [\s\S] 或类似形式匹配“任何字符”。
注意空格
通常我们很少注意空格。对我们来说,字符串 1-5 和 1 - 5 几乎相同。
但是,如果正则表达式未考虑空格,则可能无法正常工作。
让我们尝试查找由连字符(-)分隔的数字:
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之外的任何字符。
……但这还不是全部!
JavaScript 用于字符串的 Unicode 编码提供了许多字符属性,例如:这个字母属于哪一种语言(如果它是一个字母)?它是标点符号吗?等等。
我们也可以通过这些属性进行搜索。这需要标志 u,在下一篇文章中介绍。
锚点(Anchors):字符串开始 ^ 和末尾 $
插入符号 ^ 和美元符号 $ 在正则表达式中具有特殊的意义。它们被称为“锚点”。
插入符号 ^ 匹配文本开头,而美元符号 $ - 则匹配文本末尾。
举个例子,让我们测试一下文本是否以 Mary 开头:
let str1 = "Mary had a little lamb";
alert( /^Mary/.test(str1) ); // true
该模式 ^Mary 的意思是:字符串开始,接着是 “Mary”。
与此类似,我们可以用 snow$ 来测试文本是否以 snow 结尾:
let str1 = "it's fleece was white as snow";
alert( /snow$/.test(str1) ); // true
在以上这些具体的例子中我们实际上可以用 startsWith/endsWith 来代替。正则表达式应该被用于更加复杂的测试中。
测试完全匹配
这两个锚点 ^...$ 放在一起常常被用于测试一个字符串是否完全匹配一个模式。比如,测试用户的输入是否符合正确的格式。
让我们测试一下一个字符串是否属于 12:34 格式的时间。即,两个数字,然后一个冒号,接着是另外两个数字。
用正则表达式来表示就是 \d\d:\d\d:
let goodInput = "12:34";
let badInput = "12:345";
let regexp = /^\d\d:\d\d$/;
alert( regexp.test(goodInput) ); // true
alert( regexp.test(badInput) ); // false
在这个例子中 \d\d:\d\d 所对应的匹配文本必须正好在文本开头 ^ 之后,而在这之后必须紧跟文本末尾 $。
整个字符串必须准确地符合这一个格式。如果其中有任何偏差或者额外的字符,结果将为 false。
当修饰符 m 出现时,锚点将会有不同的行为。我们将在后面学习到。
锚点具有“零宽度”
锚点 ^ 和 $ 属于测试。它们的宽度为零。
换句话来说,它们并不匹配一个具体的字符,而是让正则引擎测试所表示的条件(文本开头/文本末尾)。
Flag "m" — 多行模式
通过 flag /.../m 可以开启多行模式。
这仅仅会影响 ^ 和 $ 锚符的行为。
在多行模式下,它们不仅仅匹配文本的开始与结束,还匹配每一行的开始与结束。
行的开头 ^
在这个有多行文本的例子中,正则表达式 /^\d+/gm 将匹配每一行的开头数字:
let str = `1st place: Winnie
2nd place: Piglet
33rd place: Eeyore`;
alert( str.match(/^\d+/gm) ); // 1, 2, 33
没有 flag /.../m 时,仅仅是第一个数字被匹配到:
let str = `1st place: Winnie
2nd place: Piglet
33rd place: Eeyore`;
alert( str.match(/^\d+/g) ); // 1
这是因为默认情况下,锚符 ^ 仅仅匹配文本的开头,在多行模式下,它匹配行的开头。
正则表达式引擎将会在文本中查找以锚符 ^ 开始的字符串,我们找到之后继续匹配 \d+ 模式。
行的结尾 $
美元符 $ 行为也相似。
正则表达式 `\w+$ 会找到每一行的最后一个单词:
let str = `1st place: Winnie
2nd place: Piglet
33rd place: Eeyore`;
alert( str.match(/\w+$/gim) ); // Winnie,Piglet,Eeyore
没有 /.../m flag 的话,美元符 $ 将会仅仅匹配整个文本的结尾,所以只有最后的一个单词会被找到。
锚符 ^$ 对比 \n
要寻找新的一行的话,我们不仅可以使用锚符 ^ 和 $,也可以使用换行符 \n。
它和锚符 ^ 和 $ 的第一个不同点是它不像锚符那样,它会“消耗”掉 \n 并且将其(\n)加入到匹配结果中。
举个例子,我们在下面的代码中用它来替代 $:
let str = `1st place: Winnie
2nd place: Piglet
33rd place: Eeyore`;
alert( str.match(/\w+\n/gim) ); // Winnie\n,Piglet\n
这里,我们每次匹配到的时候都会被添加一个换行符。
还有一个不同点——换行符 \n 不会匹配字符串结尾。这就是为什么在上面的例子中 Eeyore 没有匹配到。
所以,通常情况下使用锚符更棒,用它匹配出来的结果更加接近我们想要的结果。
词边界:\b
词边界 \b 是一种检查,就像 ^ 和 $ 一样。
当正则表达式引擎(实现搜索正则表达式的程序模块)遇到 \b 时,它会检查字符串中的位置是否是词边界。
有三种不同的位置可作为词边界:
- 在字符串开头,如果第一个字符是单词字符
\w。 - 在字符串中的两个字符之间,其中一个是单词字符
\w,另一个不是。 - 在字符串末尾,如果最后一个字符是单词字符
\w。
例如,可以在 Hello, Java! 中找到匹配 \bJava\b 的单词,其中 Java 是一个独立的单词,而在 Hello, JavaScript! 中则不行。
alert( "Hello, Java!".match(/\bJava\b/) ); // Java
alert( "Hello, JavaScript!".match(/\bJava\b/) ); // null
在字符串 Hello, Java! 中,以下位置对应于 \b:
因此,它与模式 \bHello\b 相匹配,因为:
- 字符串的开头符合第一种检查
\b。 - 然后匹配了单词
Hello。 - 然后与
\b再次匹配,因为我们在o和一个空格之间。
模式 \bJava\b 也同样匹配。但 \bHell\b(因为 l 之后没有词边界)和 Java!\b(因为感叹号不是单词 \w,所以其后没有词边界)却不匹配。
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
词边界 \b 不适用于非拉丁字母
词边界测试 \b 检查位置的一侧是否匹配 \w,而另一侧则不匹配 “\w”。
但是,\w 表示拉丁字母 a-z(或数字或下划线),因此此检查不适用于其他字符,如西里尔字母(cyrillic letters)或象形文字(hieroglyphs)。
转义,特殊字符
正如我们所看到的,一个反斜杠 "\" 是用来表示匹配字符类的。所以它是一个特殊字符。
还存在其它的特殊字符,这些字符在正则表达式中有特殊的含义。它们可以被用来做更加强大的搜索。
这里是包含所有特殊字符的列表:[ \ ^ $ . | ? * + ( )。
现在并不需要尝试去记住它们 —— 当我们分别处理其中的每一个时,你自然而然就会记住它们。
转义
如果要把特殊字符作为常规字符来使用,只需要在它前面加个反斜杠。
这种方式也被叫做“转义一个字符”。
比如说,我们需要找到一个点号 '.'。在一个正则表达式中一个点号意味着“除了换行符以外的任意字符”,所以如果我们想真正表示对“一个点号”查询的时候,可以在点号前加一个反斜杠。
alert( "Chapter 5.1".match(/\d\.\d/) ); // 5.1
括号也是特殊字符,所以如果我们想要在正则中查找它们,我们应该使用 \(。下面的例子会查找一个字符串 "g()":
alert( "function g()".match(/g\(\)/) ); // "g()"
如果我们想查找反斜杠 \,我们就应该使用两个反斜杠来查找:
alert( "1\\2".match(/\\/) ); // '\'
一个斜杠
斜杠符号 '/' 并不是一个特殊符号,但是它被用于在 Javascript 中开启和关闭正则匹配:/...pattern.../,所以我们也应该转义它。
下面是查询斜杠 '/' 的表达式:
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
在字符串中的反斜杠表示转义或者类似 \n 这种只能在字符串中使用的特殊字符。这个引用会“消费”并且解释这些字符,比如说:
\n—— 变成一个换行字符,\u1234—— 变成包含该码位的 Unicode 字符,- 。。。其它有些并没有特殊的含义,就像
\d或者\z,碰到这种情况的话会把反斜杠移除。
所以调用 new RegExp 会获得一个没有反斜杠的字符串。
如果要修复这个问题,我们需要双斜杠,因为引用会把 \\ 变为 \:
let regStr = "\\d\\.\\d";
alert(regStr); // \d\.\d (correct now)
let regexp = new RegExp(regStr);
alert( "Chapter 5.1".match(regexp) ); // 5.1
Summary
- 要在字面(意义)上搜索特殊字符
[ \ ^ $ . | ? * + ( ),我们需要在它们前面加上反斜杠\(“转义它们”)。 - 如果我们在
/.../内部(但不在new RegExp内部),还需要转义/。 - 传递一个字符串(参数)给
new RegExp时,我们需要双倍反斜杠\\,因为字符串引号会消费其中的一个。
集合和范围 [...]
在方括号 […] 中的几个字符或者字符类意味着“搜索给定的字符中的任意一个”。
集合
比如说,[eao] 意味着查找在 3 个字符 'a'、'e' 或者 `‘o’ 中的任意一个。
这被叫做一个集合。集合可以在正则表达式中和其它常规字符一起使用。
// 查找 [t 或者 m],然后再匹配 “op”
alert( "Mop top".match(/[tm]op/gi) ); // "Mop", "top"
请注意尽管在集合中有多个字符,但它们在匹配中只会对应其中的一个。
所以下面的示例并不会匹配上:
// 查找 “V”,然后匹配 [o 或者 i],之后再匹配 “la”
alert( "Voila".match(/V[oi]la/) ); // null,并没有匹配上
这个模式会做以下假设:
V,- 然后匹配其中的一个字符
[oi], - 然后匹配
la,
所以可以匹配上 Vola 或者 Vila。
范围
方括号也可以包含字符范围。
比如说,[a-z] 会匹配从 a 到 z 范围内的字母,[0-5] 表示从 0 到 5 的数字。
在下面的示例中,我们会查询首先匹配 "x" 字符,再匹配两个数字或者位于 A 到 F 范围内的字符。
alert( "Exception 0xAF".match(/x[0-9A-F][0-9A-F]/g) ); // xAF
[0-9A-F] 表示两个范围:它搜索一个字符,满足数字 0 到 9 或字母 A 到 F。
如果我们还想查找小写字母,则可以添加范围 a-f:[0-9A-Fa-f]。或添加标志 i。
我们也可以在 […] 里面使用字符类。
例如,如果我们想要查找单词字符 \w 或连字符 -,则该集合为 [\w-]。
也可以组合多个类,例如 [\s\d] 表示 “空格字符或数字”。
字符类是某些字符集的简写
例如:
- \d —— 和
[0-9]相同, - \w —— 和
[a-zA-Z0-9_]相同, - \s —— 和
[\t\n\v\f\r ]外加少量罕见的 unicode 空格字符相同。
排除范围
除了普通的范围匹配,还有类似 [^…] 的“排除”范围匹配。
它们通过在匹配查询的开头添加插入符号 ^ 来表示,它会匹配所有除了给定的字符之外的任意字符。
比如说:
[^aeyo]—— 匹配任何除了'a'、'e'、'y'或者'o'之外的字符。[^0-9]—— 匹配任何除了数字之外的字符,也可以使用\D来表示。[^\s]—— 匹配任何非空字符,也可以使用\S来表示。
下面的示例查询除了字母,数字和空格之外的任意字符:
alert( "alice15@gmail.com".match(/[^\d\sA-Z]/gi) ); // @ and .
[在 …] 中不转义
通常当我们的确需要查询点字符时,我们需要把它转义成像 \. 这样的形式。如果我们需要查询一个反斜杠,我们需要使用 \\。
在方括号表示中,绝大多数特殊字符可以在不转义的情况下使用:
- 表示一个点符号
'.'。 - 表示一个加号
'+'。 - 表示一个括号
'( )'。 - 在开头或者结尾表示一个破折号(在这些位置该符号表示的就不是一个范围) `pattern:’-’。
- 在不是开头的位置表示一个插入符号(在开头位置该符号表示的是排除)
'^'。 - 表示一个开口的方括号符号
'['。
换句话说,除了在方括号中有特殊含义的字符外,其它所有特殊字符都是允许不添加反斜杠的。
一个在方括号中的点符号 "." 表示的就是一个点字符。查询模式 [.,] 将会寻找一个为点或者逗号的字符。
在下面的示例中,[-().^+] 会查找 -().^+ 的其中任意一个字符:
// 并不需要转义
let reg = /[-().^+]/g;
alert( "1 + 2 - 3".match(reg) ); // 匹配 +,-
。。。但是如果你为了“以防万一”转义了它们,这也不会有任何问题:
//转义其中的所有字符
let reg = /[\-\(\)\.\^\+]/g;
alert( "1 + 2 - 3".match(reg) ); // 仍能正常工作:+,-
量词 +,*,? 和 {n}
假设我们有一个字符串 +7(903)-123-45-67,并且想要找到它包含的所有数字。但与之前不同的是,我们对单个数字不感兴趣,只对全数感兴趣:7, 903, 123, 45, 67。
数字是一个或多个 \d 的序列。用来形容我们所需要的数量的词被称为量词。
数量 {n}
最明显的量词便是一对引号间的数字:{n}。在一个字符(或一个字符类等等)后跟着一个量词,用来指出我们具体需要的数量。
它有更高级的格式,用一个例子来说明:
-
确切的位数:
{5}\d{5}表示 5 位的数字,如同\d\d\d\d\d。接下来的例子将会查找一个五位数的数字:alert( "I'm 12345 years old".match(/\d{5}/) ); // "12345"我们可以添加\b来排除更多位数的数字:\b\d{5}\b。 -
某个范围的位数:
{3,5}我们可以将限制范围的数字放入括号中,来查找位数为 3 至 5 位的数字:
\d{3,5}``alert( "I'm not 12, but 1234 years old".match(/\d{3,5}/) ); // "1234"我们可以省略上限。那么正则表达式\d{3,}就会查找位数大于或等于 3 的数字:alert( "I'm not 12, but 345678 years old".match(/\d{3,}/) ); // "345678"
对于字符串 +7(903)-123-45-67 来说,我们如果需要一个或多个连续的数字,就使用 \d{1,}:
let str = "+7(903)-123-45-67";
let numbers = str.match(/\d{1,}/g);
alert(numbers); // 7,903,123,45,67
缩写
大多数常用的量词都可以有缩写:
-
+代表“一个或多个”,相当于
{1,}。例如,\d+用来查找所有数字:let str = "+7(903)-123-45-67"; alert( str.match(/\d+/g) ); // 7,903,123,45,67 -
?代表“零个或一个”,相当于
{0,1}。换句话说,它使得符号变得可选。例如,模式ou?r查找o,后跟零个或一个u,然后是r。所以他能够在color中找到or,以及在colour中找到our:let str = "Should I write color or colour?"; alert( str.match(/colou?r/g) ); // color, colour -
*代表着“零个或多个”,相当于
{0,}。也就是说,这个字符可以多次出现或不出现。接下来的例子将要寻找一个后跟任意数量的 0 的数字:alert( "100 10 1".match(/\d0*/g) ); // 100, 10, 1将它与'+'(一个或多个)作比较:alert( "100 10 1".match(/\d0+/g) ); // 100, 10
更多示例
量词是经常被使用的。它们是构成复杂的正则表达式的主要模块之一,我们接着来看更多的例子。
-
正则表达式“浮点数”(带浮点的数字):
\d+\.\d+实现:
alert( "0 1 12.345 7890".match(/\d+\.\d+/g) ); // 12.345 -
正则表达式“打开没有属性的 HTML 标记”,比如
<span>或<p>:/<[a-z]+>/i实现:
alert( "<body> ... </body>".match(/<[a-z]+>/gi) ); // <body>我们查找字符'<'后跟一个或多个英文字母,然后是'>'。 -
正则表达式“打开没有属性的HTML标记”(改进版):
/<[a-z][a-z0-9]*>/i更好的表达式:根据标准,HTML 标记名称可以在除了第一个位置以外的任意一个位置有一个数字,比如
<h1>。alert( "<h1>Hi!</h1>".match(/<[a-z][a-z0-9]*>/gi) ); // <h1> -
正则表达式“打开没有属性的HTML标记”:
/<\/?[a-z][a-z0-9]*>/i我们在标记前加上了一个可选的斜杆
/?。必须用一个反斜杠来转义它,否则 JavaScript 就会认为它是这个模式的结束符。alert( "<h1>Hi!</h1>".match(/<\/?[a-z][a-z0-9]*>/gi) ); // <h1>, </h1>
更精确意味着更复杂
我们能够从这些例子中看到一个共同的规则:正则表达式越精确 —— 它就越长且越复杂。
例如,HTML 标记能用一个简单的正则表达式:<\w+>。
因为 \w 代表任意英文字母或数字或 '_',这个正则表达式也能够匹配非标注的内容,比如 <_>。但它要比 <[a-z][a-z0-9]*> 简单很多。
我们能够接受 <\w+> 或者我们需要 <[a-z][a-z0-9]*>?
在现实生活中,两种方式都能接受。取决于我们对于“额外”匹配的宽容程度以及是否难以通过其他方式来过滤掉它们。