模糊匹配
横向模糊匹配
特点:一个正则可匹配的字符串的长度不固定。
其实现的方式是使用量词。譬如 {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,},表示出现任意次,有可能不出现 | 
贪婪匹配与惰性匹配
var regex = /\d{2,5}/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
// => ["123", "1234", "12345", "12345"]
上面例子的正则表达式/\d{2,5}/g可以匹配2-5个数字字符,但它是贪婪的,会尽可能多的匹配更多的字符。
var regex = /\d{2,5}?/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
// => ["12", "12", "34", "12", "34", "12", "34", "56"]
其中/\d{2,5}?/表示,虽然 2 到 5 次都行,当2个就够的时候,就不再往下尝试了。
在量词后面增加?,即可将两次变为贪婪量词
多选分支
具体形式如下:(p1|p2|p3),其中 p1、p2 和 p3 是子模式,用 |(管道符)分隔,表示其中任何之一。
例如要匹配字符串"good" 和 "nice" 可以使用 /good|nice/
注意,比如我用/good|goodbye/,去匹配"goodbye" 字符串时,结果是 "good",而把正则改成/goodbye|good/,结果是:"goodbye"。
也就是说,分支结构也是惰性的,即当前面的匹配上了,后面的就不再尝试了。
位置匹配
位置(锚)是相邻字符之间的位置。比如,下图中箭头所指的地方:
在 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 的,我们可以先写一个简单的正则
var regex = /\d{4}-\d{2}-\d{2}/;
然后再修改成括号版的:
var regex = /(\d{4})-(\d{2})-(\d{2})/;
在匹配过程中,正则引擎给每一个分组都开辟一个空间,用来存储每一个分组匹配到的数据。
既然分组可以捕获数据,那么我们就可以使用它们。
提取数据
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
console.log( string.match(regex) );
// => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]
match返回的一个数组,第一个元素是整体匹配结果,然后是各个分组(括号里)匹配的内容,然后是匹配下标,最后是输入的文本。另外,正则表达式是否有修饰符 g,match返回的数组格式是不一样的。
同时,也可以使用构造函数的全局属性 $1 至 $9 来获取:
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
regex.test(string); // 正则操作即可,例如
//regex.exec(string);
//string.match(regex);
console.log(RegExp.$1); // "2017"
console.log(RegExp.$2); // "06"
console.log(RegExp.$3); // "12"
替换
比如,想把yyyy-mm-dd 格式,替换成mm/dd/yyyy
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, "$2/$3/$1");
console.log(result);
// => "06/12/2017"
其中replace 中的,第二个参数里用$1、$2、$3指代相应的分组。等价于如下的两种形式:
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, function () {
return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1;
});
console.log(result);
// => "06/12/2017"
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, function (match, year, month, day) {
return month + "/" + day + "/" + year;
});
console.log(result);
// => "06/12/2017"
反向引用
除了使用相应 API 来引用分组,也可以在正则本身里引用分组。但只能引用之前出现的分组,即反向引用。
还是以日期为例。
比如要写一个正则支持匹配如下三种格式:
2016-06-12
2016/06/12
2016.06.12
最先可能想到的正则是:
var regex = /\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // true
虽然匹配了要求的情况,但也匹配 "2016-06/12"这样的数据。
假设我们想要求分割符前后一致怎么办?此时需要使用反向引用:
var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // false
注意里面的 \1,表示的引用之前的那个分组 (-|\/|\.)。不管它匹配到什么(比如-),\1都匹配那个同
样的具体某个字符。
我们知道了 \1 的含义后,那么\2和\3的概念也就理解了,即分别指代第二个和第三个分组。
括号嵌套情况
var regex = /^((\d)(\d(\d)))\1\2\3\4$/;
var string = "1231231233";
console.log( regex.test(string) ); // true
console.log( RegExp.$1 ); // 123
console.log( RegExp.$2 ); // 1
console.log( RegExp.$3 ); // 23
console.log( RegExp.$4 ); // 3
我们可以看看这个正则匹配模式:
第一个字符是数字,比如说"1",
第二个字符是数字,比如说"2",
第三个字符是数字,比如说"3",
接下来的是 \1,是第一个分组内容,那么看第一个开括号对应的分组是什么,是"123",
接下来的是 \2,找到第2个开括号,对应的分组,匹配的内容是 "1",
接下来的是 \3,找到第3个开括号,对应的分组,匹配的内容是"23",
最后的是\4,找到第3个开括号,对应的分组,匹配的内容是"3"。
 
                         
                                

