正则表达式一篇入门

489 阅读20分钟

模式(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

    使用此修饰符后,搜索时不区分大小写: Aa 没有区别(具体看下面的例子)。

  • 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. 第一个搜索返回的是 -1(也就是没找到),因为搜索默认是区分大小写的。
  2. 使用修饰符 /LOVE/i,在字符串的第 2 个位置上搜索到了 love

相比与简单的子字符串查找,i 修饰符已经让正则表达式变得更加强大了。但是这还不够。我们会在下一章节讲述其它修饰符和特性。

总结

  • 一个正则表达式包含模式和可选修饰符:gimuy
  • 如果不使用我们在后面将要学到的修饰符和特殊标志,正则表达式的搜索就等同于子字符串查找。
  • 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”)

    数字:从 09 的字符。

  • \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-51 - 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 相匹配,因为:

  1. 字符串的开头符合第一种检查 \b
  2. 然后匹配了单词 Hello
  3. 然后与 \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] 会匹配从 az 范围内的字母,[0-5] 表示从 05 的数字。

在下面的示例中,我们会查询首先匹配 "x" 字符,再匹配两个数字或者位于 AF 范围内的字符。

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

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

如果我们还想查找小写字母,则可以添加范围 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 中找到 ourlet 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]*>

在现实生活中,两种方式都能接受。取决于我们对于“额外”匹配的宽容程度以及是否难以通过其他方式来过滤掉它们。

参考文献

zh.javascript.info/regular-exp…