JavaScript系列——正则表达式

需求场景

在前端开发领域,在日常的开发任务中,你是否遇到以下的需求:

例如:

  • 前端表单校验手机号码是否符合规则?
  • 用户提交表单中的身份证件号是否符合大陆居民身份证规则?
  • 如何提取字符串中出现的数字?
  • …等等等

上述类似的需求我们在日常开发中,会遇到很多,JavaScript 为我们提供了一套解决方案,就是正则表达式,我们可以提取需要识别的目标的轮廓,用其构造一个正则表达式对象,通过方法来提取目标对象或者判断是否符合某些规则,下面就来具体说明正则表达式。

正则表达式的定义

正则表达式是用于匹配字符串中的字符组合的模式。它是一个对象,这个对象可以被用于 RegExp(正则表达式)的exec/test和字符串的 match/matchAll/replace/search/split 方法。

创建正则表达式

我们可以使用以下两种方法构建一个正则表达式:

通过 / 表示式/ 创建

var re = /ab+c/;

通过构造函数创建

var re = new RegExp("ab+c");

当JavaScript语句加载后,正则表达式字面量就会被编译,当其保持不变时,使用字面量创建方法,可以提高性能

编写一个正则表达式的模式

一个正则表达式模式是由字符串构成的,这些字符串可以是数字、字母、特殊符号的组合。

使用简单模式

简单模式直接使用目标字符串构成,比如/abc/ 就是要找到出现abc的字符串,如果abc中间出现空格,那么这种简单的模式,就不能匹配,无法满足我们的需求,就需要用到下面带有特殊字符的模式,这些特殊的字符在正则表示式中,有特别的含义

使用特殊字符

当我们需要匹配一个不太确定的字符串时,比如寻找一个或多个“a” ,或者寻找空格,可以在模式中使用特殊字符,比如使用/ab*c/去匹配单独的“a”后面跟了零个或多个“b”,同时后面跟着“c”的字符串,特殊字符*的含义是,前一项的字符出现零次或者多次。

常用特殊字符列表

下面的页面与表格列出了一个正则表达式中可以利用的特殊字符的完整列表和描述。

特殊字符
字符含义
\(1)在非特殊字符之前的反斜杠表示下一个字符就是特殊字符,此字符不能按照字面理解,面。例如前面没有 “” 的 “b” 通常匹配小写字母 “b”,但如果前面出现\,它则不会匹配任何字符,在特殊字符之前的反斜杠表示下一个字符不是特殊字符,能够转义
^表示匹配以下一个字符开始的字符串,例如,/^A/ 并不会匹配 “an A” 中的 ‘A’,但是会匹配 “An E” 中的 ‘A’。
$表示匹配以上一个字符结尾的字符串,例如,/t$/ 并不会匹配 “eater” 中的 ‘t’,但是会匹配 “eat” 中的 ‘t’。
*匹配一个表达式0次货多次,等价于{0,},例如,/bo*/ 会匹配 “A ghost boooooed” 中的 ‘booooo’ 和 “A bird warbled” 中的 ‘b’,但是在 “A goat grunted” 中不会匹配任何内容。
+匹配前面表达式1次或者多次,等价于{1,},例如,/a+/ 会匹配 “candy” 中的 ‘a’ 和 “caaaaaaandy” 中所有的 ‘a’,但是在 “cndy” 中不会匹配任何内容。
?匹配前面表达式出现0次或1次,等价于{0,1},例如,/e?le?/ 匹配 “angel” 中的 ‘el’、“angle” 中的 ‘le’ 以及 "oslo’ 中的 ‘l’。如果这个符号紧跟着任何量词 *、 +、? 或 {} 的后面,则会使得量词变成非贪婪,和没有?符号使用的贪婪模式(匹配尽可能多的字符)正好相反 。例如,对 “123abc” 使用 /\d+/ 将会匹配 “123”,而使用 /\d+?/ 则只会匹配到 “1”。
.(小数点)默认匹配除换行符之外的任何单个字符。例如,/.n/ 将会匹配 “nay, an apple is on the tree” 中的 ‘an’ 和 ‘on’,但是不会匹配 ‘nay’。
组和范围

下面这些组合,表示表达式字符的分组和范围

模式含义
(x)像下面的例子展示的那样,它会匹配 ‘x’ 并且记住匹配项。其中括号被称为捕获括号。/(foo) (bar) \1 \2/ 中的 ‘(foo)’ 和 ‘(bar)’ 匹配并记住字符串 “foo bar foo bar” 中前两个单词。模式中的 \1 和 \2 表示第一个和第二个被捕获括号匹配的子字符串,即 foo 和 bar,匹配了原字符串中的后两个单词。
(?:x)匹配 'x' 但是不记住匹配项,这种括号叫作非捕获括号,使得你能够定义与正则表达式运算符一起使用的子表达式。看看这个例子 /(?:foo){1,2}/,{1,2} 会应用于整个 'foo' 单词 ,如果表达式是 /foo{1,2}/,{1,2} 将只应用于 ‘foo’ 的最后一个字符 'o'
x|y匹配‘x’或者‘y’
[xyz]一个字符集合。匹配方括号中的任意字符,包括转义序列。你可以使用破折号(-)来指定一个字符范围。对于点(.)和星号(*)这样的特殊符号在一个字符集中没有特殊的意义。他们不必进行转义,不过转义也是起作用的。例如,[abcd] 和 [a-d] 是一样的。他们都匹配"brisket"中的‘b’,也都匹配“city”中的‘c’。/[a-z.]+/ 和/[\w.]+/与字符串“test.i.ng”匹配。
{n}n 是一个正整数,匹配了前面一个字符刚好出现了 n 次。
{n,}n 是一个正整数,匹配前一个字符至少出现了 n 次。
{n,m}n 和 m 都是整数。匹配前面的字符至少 n 次,最多 m 次。如果 n 或者 m 的值是 0,这个值被忽略。
[\b]匹配一个退格 (U+0008)。(不要和\b混淆了。)
\b匹配一个词的边界。一个词的边界就是一个词不被另外一个“字”字符跟随的位置或者前面跟其他“字”字符的位置,例如在字母和空格之间。注意,匹配中不包括匹配的字边界。换句话说,一个匹配的词的边界的内容的长度是 0。(不要和 [\b] 混淆了),例如使用"moon例:/\bm/匹配“moon”中的‘m’;/oo\b/并不匹配"moon"中的’oo’,因为’oo’被一个“字”字符’n’紧跟着。/oon\b/匹配"moon"中的’oon’,因为’oon’是这个字符串的结束部分。这样他没有被一个“字”字符紧跟着。/\w\b\w/将不能匹配任何字符串,因为在一个单词中间的字符永远也不可能同时满足没有“字”字符跟随和有“字”字符跟随两种情况。
\s匹配一个空白字符,包括空格、制表符、换页符和换行符。等价于 [\f\n\r\t\v\u0020\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。
\S匹配一个非空白字符。
\w匹配一个单字字符(字母、数字或者下划线)。等价于 [A-Za-z0-9_]。
\W匹配一个非单字字符。等价于 [^A-Za-z0-9_]。
\d匹配一个数字。等价于 [0-9]。
[^xyz]一个反向字符集。也就是说, 它匹配任何没有包含在方括号中的字符。你可以使用破折号(-)来指定一个字符范围。任何普通字符在这里都是起作用的。
\D匹配一个非数字字符。等价于 [^0-9]。

正则表达式使用

下面表格整理了 正则表达式搭配使用的方法

方法描述
exec在指定的字符串中寻找匹配正则表达式的元素的RegExp 方法,他返回一个数组,未匹配返回null
test返回在一个字符串中,是否存在符合正则表达式的子串的RegExp 方法,值是true或false
match一个在字符串中执行查找匹配的 String 方法,它返回一个数组,在未匹配到时会返回 null。
matchAll一个在字符串中执行查找所有匹配的 String 方法,它返回一个迭代器(iterator)。
search一个在字符串中测试匹配的 String 方法,它返回匹配到的位置索引,或者在失败时返回 -1。
replace一个在字符串中执行查找匹配的 String 方法,并且使用替换字符串替换掉匹配到的子字符串。
split一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的 String 方法。

说明:
如果想要知道某些字符串是否存在,你可以使用 test 或 search 方法。想得到更多的信息(但是比较慢)则可以使用 exec 或 match 方法

代码演示

在接下来的例子中,脚本将使用 exec 方法在一个字符串中查找一个匹配。

var myRe = /d(b+)d/g;
var myArray = myRe.exec("cdbbdbsbz");

如果你不需要访问正则表达式的属性,这个脚本通过另一个方法来创建 myArray:

var myArray = /d(b+)d/g.exec("cdbbdbsbz");
// 和 "cdbbdbsbz".match(/d(b+)d/g); 相似。
// 但是 "cdbbdbsbz".match(/d(b+)d/g) 输出数组 [ "dbbd" ],
// 而 /d(b+)d/g.exec('cdbbdbsbz') 输出数组 [ "dbbd", "bb", index: 1, input: "cdbbdbsbz" ].

如果你想通过一个字符串构建正则表达式,那么这个脚本还有另一种方法:

var myRe = new RegExp("d(b+)d", "g");
var myArray = myRe.exec("cdbbdbsbz");

通过这些脚本,匹配成功后将返回一个数组并且更新正则表达式的属性,如下表所示。

在这里插入图片描述
上述例子中,使用了g 这个高级标志,下列还有更多列举一下

标志描述
g全局检索,从开头到结尾匹配,而不是一匹配就结束
m多行检索
i不区分大小写

为了在正则表达式中包含标志,请使用以下语法:

var re = new RegExp("pattern", "flags");

或者

var re = /pattern/flags;

例如,re = /\w+\s/g 将创建一个查找一个或多个字符后有一个空格的正则表达式,或者组合起来像此要求的字符串。

var re = /\w+\s/g;
var str = "fee fi fo fum";
var myArray = str.match(re);
console.log(myArray);

// ["fee ", "fi ", "fo "]

常用示例

验证手机号码合法性

验证手机号码 形如 :136-2119-9812 ,136/2119/9812,136.2119.9812,13621199812 格式。

  var re = /(?:\d{3}|\(\d{3}\))([-\/\.]{0,1})\d{4}\1\d{4}/;
      function testInfo(phoneInput) {
        var OK = re.exec(phoneInput.value);
        if (!OK)
          window.alert(
            phoneInput.value + " isn't a phone number with area code!",
          );
        else window.alert("Thanks, your phone number is " + OK[0]);
      }

上面代码,通过字面量创建一个正则表达式,
(?: 这个正则表达式寻找三个数字字符 \d{3}, 或者 | 一个左半括号 \( 跟着三位数字 \d{3}, 跟着一个封闭括号 \), (结束非捕获括号 ))。表示前面三个是数字。

后跟着一个短破折号或正斜杠或小数点,随后跟随4个数字字符,当记忆字符 ([-\/\.]) 捕获并记住,里面字符可以出现0次或一次,后面跟着四位小数 \d{4},再后面跟随记住的破折号、正斜杠或小数点 \1,最后跟着四位小数 \d{4}。

小结

  • 正则表达式中属于 RegExp 方法有 exec、test,其中exec 返回匹配的数组(可以是多个),所以0是最长的匹配子串,test返回的是布尔值
  • match 和matchAll 、search、replace和split 属于字符串的方法。
  • match 返回也是数组,但只有一个元素,最长的子串
  • matchAll 返回一个迭代器,查找所有匹配的子串