模糊匹配

横向模糊匹配

特点:一个正则可匹配的字符串的长度不固定。

其实现的方式是使用量词。譬如 {m,n},表示连续出现最少 m 次,最多 n 次。

比如正则/ab{2,5}c/表示匹配这样一个字符串:第一个字符是 “a”,接下来是 2 到 5 个 “b”,最后是字符 “c”。

纵向模糊匹配

特点:一个正则匹配的字符串,具体到某一位字符时,可以有多种可能。

其实现的方式是使用字符组。譬如[abc],表示该字符是可以字符 "a"、"b"、"c"中的任何一个

比如/a[123]b/可以匹配如下三种字符串:"a1b"、"a2b"、"a3b"

字符组

字符组(字符类),只表示组内的一个字符。

例如[abc],表示匹配一个字符,它可以是 "a"、"b"、"c"之一。

范围表示

如果字符组里的字符特别多,可以使用范围表示法。

比如[123456abcdefGHIJKLM],可以写成 [1-6a-fG-M]。用连字符- 来省略和简写。

因为连字符有特殊用途,那么要匹配 "a"、"-"、"z"
可以写成如下的方式:[-az][az-][a\-z]
即要么放在开头,要么放在结尾,要么转义,不让引擎认为是范围表示法就行了。

排除字符

纵向模糊匹配,还有一种情形就是,某位字符可以是任何东西,但就不能是"a"、"b"、"c"

此时就是排除字符组(反义字符组)的概念。例如[^abc],表示是一个除 "a"、"b"、"c"之外的任意一个字符。字符组的第一位放^(脱字符),表示求反的概念。

字符组的简写

字符组 具体含义
\d 表示 [0-9],是一位数字
\D 表示 [^0-9],除数字外的任意字符
\w 表示 [0-9a-zA-Z_]。表示数字、大小写字母和下划线。
\W 表示 [^0-9a-zA-Z_]。非单词字符。
\s 表示 [ \t\v\n\r\f]。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符。
\S 表示 [^ \t\v\n\r\f]。 非空白符
. 表示 [^\n\r\u2028\u2029]。通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分隔符除外

可以使用[\d\D][\w\W][\s\S][^] 中任何的一个来代表匹配任意一个字符。

量词

简写形式

量词 具体含义
{m,} 表示至少出现 m 次
{m} 等价于{m,m},表示出现 m 次
? 等价于{0,1},表示出现或者不出现
+ 等价于{1,},表示出现至少一次
* 等价于{0,},表示出现任意次,有可能不出现

贪婪匹配与惰性匹配

  1. var regex = /\d{2,5}/g;
  2. var string = "123 1234 12345 123456";
  3. console.log( string.match(regex) );
  4. // => ["123", "1234", "12345", "12345"]

上面例子的正则表达式/\d{2,5}/g可以匹配2-5个数字字符,但它是贪婪的,会尽可能多的匹配更多的字符。

  1. var regex = /\d{2,5}?/g;
  2. var string = "123 1234 12345 123456";
  3. console.log( string.match(regex) );
  4. // => ["12", "12", "34", "12", "34", "12", "34", "56"]

其中/\d{2,5}?/表示,虽然 2 到 5 次都行,当2个就够的时候,就不再往下尝试了。

在量词后面增加,即可将两次变为贪婪量词

多选分支

具体形式如下:(p1|p2|p3),其中 p1p2p3 是子模式,用 |(管道符)分隔,表示其中任何之一。

例如要匹配字符串"good""nice" 可以使用 /good|nice/

注意,比如我用/good|goodbye/,去匹配"goodbye" 字符串时,结果是 "good",而把正则改成/goodbye|good/,结果是:"goodbye"

也就是说,分支结构也是惰性的,即当前面的匹配上了,后面的就不再尝试了。

位置匹配

位置(锚)是相邻字符之间的位置。比如,下图中箭头所指的地方:
JavaScript正则表达式随记 - 图1

在 ES5 中,共有 6 个锚:
^、$、\b、\B、(?=p)、(?=p)

具体含义
^ 匹配开头,在多行匹配中匹配行开头
$ 匹配结尾,在多行匹配中匹配行结尾
\b 是单词边界,具体就是\w\W之间的位置,也包括 \w^ 之间的位置,和\w 与 $之间的位置
\B \B 就是\b 的反面的意思,非单词边界。在字符串中所有位置中,扣掉 \b,剩下的都是\B
(?=p) (?=p),其中p是一个子模式,即p前面的位置,或者说,该位置后面的字符要匹配 p
(?=p) (?!p) 就是 (?=p) 的反面意思

分组和分支结构

分组

/a+/ 匹配连续出现的 "a",而要匹配连续出现的 "ab" 时,需要使用 /(ab)+/

其中括号是提供分组功能,使量词 + 作用于 "ab" 这个整体

分支结构

而在多选分支结构 (p1|p2) 中,此处括号的作用也是不言而喻的,提供了分支表达式的所有可能。

分组引用

这是括号一个重要的作用,有了它,我们就可以进行数据提取,以及更强大的替换操作。

而要使用它带来的好处,必须配合使用实现环境的 API。

以日期为例。假设格式是 yyyy-mm-dd 的,我们可以先写一个简单的正则

  1. var regex = /\d{4}-\d{2}-\d{2}/;

然后再修改成括号版的:

  1. var regex = /(\d{4})-(\d{2})-(\d{2})/;

在匹配过程中,正则引擎给每一个分组都开辟一个空间,用来存储每一个分组匹配到的数据。

既然分组可以捕获数据,那么我们就可以使用它们。

提取数据

  1. var regex = /(\d{4})-(\d{2})-(\d{2})/;
  2. var string = "2017-06-12";
  3. console.log( string.match(regex) );
  4. // => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]

match返回的一个数组,第一个元素是整体匹配结果,然后是各个分组(括号里)匹配的内容,然后是匹配下标,最后是输入的文本。另外,正则表达式是否有修饰符 gmatch返回的数组格式是不一样的。

同时,也可以使用构造函数的全局属性 $1 至 $9 来获取:

  1. var regex = /(\d{4})-(\d{2})-(\d{2})/;
  2. var string = "2017-06-12";
  3. regex.test(string); // 正则操作即可,例如
  4. //regex.exec(string);
  5. //string.match(regex);
  6. console.log(RegExp.$1); // "2017"
  7. console.log(RegExp.$2); // "06"
  8. console.log(RegExp.$3); // "12"

替换

比如,想把yyyy-mm-dd 格式,替换成mm/dd/yyyy

  1. var regex = /(\d{4})-(\d{2})-(\d{2})/;
  2. var string = "2017-06-12";
  3. var result = string.replace(regex, "$2/$3/$1");
  4. console.log(result);
  5. // => "06/12/2017"

其中replace 中的,第二个参数里用$1、$2、$3指代相应的分组。等价于如下的两种形式:

  1. var regex = /(\d{4})-(\d{2})-(\d{2})/;
  2. var string = "2017-06-12";
  3. var result = string.replace(regex, function () {
  4. return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1;
  5. });
  6. console.log(result);
  7. // => "06/12/2017"
  1. var regex = /(\d{4})-(\d{2})-(\d{2})/;
  2. var string = "2017-06-12";
  3. var result = string.replace(regex, function (match, year, month, day) {
  4. return month + "/" + day + "/" + year;
  5. });
  6. console.log(result);
  7. // => "06/12/2017"

反向引用

除了使用相应 API 来引用分组,也可以在正则本身里引用分组。但只能引用之前出现的分组,即反向引用。

还是以日期为例。

比如要写一个正则支持匹配如下三种格式:

  1. 2016-06-12
  2. 2016/06/12
  3. 2016.06.12

最先可能想到的正则是:

  1. var regex = /\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/;
  2. var string1 = "2017-06-12";
  3. var string2 = "2017/06/12";
  4. var string3 = "2017.06.12";
  5. var string4 = "2016-06/12";
  6. console.log( regex.test(string1) ); // true
  7. console.log( regex.test(string2) ); // true
  8. console.log( regex.test(string3) ); // true
  9. console.log( regex.test(string4) ); // true

虽然匹配了要求的情况,但也匹配 "2016-06/12"这样的数据。

假设我们想要求分割符前后一致怎么办?此时需要使用反向引用:

  1. var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
  2. var string1 = "2017-06-12";
  3. var string2 = "2017/06/12";
  4. var string3 = "2017.06.12";
  5. var string4 = "2016-06/12";
  6. console.log( regex.test(string1) ); // true
  7. console.log( regex.test(string2) ); // true
  8. console.log( regex.test(string3) ); // true
  9. console.log( regex.test(string4) ); // false

注意里面的 \1,表示的引用之前的那个分组 (-|\/|\.)。不管它匹配到什么(比如-),\1都匹配那个同
样的具体某个字符。
我们知道了 \1 的含义后,那么\2\3的概念也就理解了,即分别指代第二个和第三个分组。

括号嵌套情况

  1. var regex = /^((\d)(\d(\d)))\1\2\3\4$/;
  2. var string = "1231231233";
  3. console.log( regex.test(string) ); // true
  4. console.log( RegExp.$1 ); // 123
  5. console.log( RegExp.$2 ); // 1
  6. console.log( RegExp.$3 ); // 23
  7. console.log( RegExp.$4 ); // 3

我们可以看看这个正则匹配模式:

第一个字符是数字,比如说"1"

第二个字符是数字,比如说"2"

第三个字符是数字,比如说"3"

接下来的是 \1,是第一个分组内容,那么看第一个开括号对应的分组是什么,是"123"

接下来的是 \2,找到第2个开括号,对应的分组,匹配的内容是 "1"

接下来的是 \3,找到第3个开括号,对应的分组,匹配的内容是"23"

最后的是\4,找到第3个开括号,对应的分组,匹配的内容是"3"