正则高级用法

3,771 阅读2分钟

定位符

字符描述
^匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与 \n 或 \r 之后的位置匹配。
$匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与 \n 或 \r 之前的位置匹配
\b匹配一个单词边界,即字与空格间的位置。
\B非单词边界匹配。

注意:不能将限定符与定位符一起使用。由于在紧靠换行或者单词边界的前面或后面不能有一个以上位置,因此不允许诸如 ^* 之类的表达式。

限定符

X ?x ,一次或一次也没有
X *X ,零次或多次
X+X ,一次或多次
X {n}X ,恰好 n 次
X {n,}X ,至少 n 次
X{n,m}X ,至少 n 次,但是不超过 m 次

但是我们如果要对多个字符进行重复怎么办呢?此时我们就要用到分组,我们可以使用小括号"()"来指定要重复的子表达式,然后对这个子表达式进行重复,例如:(abc)? 表示0个或1个abc 这里一 个括号的表达式就表示一个分组 。

分组可以分为两种形式,捕获组和非捕获组。

惰性和贪婪

.*? 或 .*+可以从贪婪变成惰性(后边多一个?表示懒惰模式)

<img src="test.jpg" width="60px" height="80px"/>

如果用正则匹配src中内容非懒惰(贪婪)模式匹配:src=".*"

匹配结果是:src="test.jpg" width="60px" height="80px" 意思是从="往后匹配,直到最后一个"匹配结束

懒惰模式正则: src=".*?"

结果:src="test.jpg" 因为匹配到第一个"就结束了一次匹配。不会继续向后匹配。因为他懒惰嘛。

使用具体型字符组来代替通配符,来消除回溯

/"[^"]*"/ 代替 /".*?"/

不匹配任何东西的正则

/.^/

获取分组方法

  1. match exec
 arr[n] = str.match(reg);     
 arr[n] = reg.exec(str);

返回的匹配数组arr[n]中,arr[0]表示整个匹配,arr[1],arr[2].......分别表示各个分组的匹配结果

  1. 通过RegExp对象的静态属性来获取

RegExp.1,RegExp.1,RegExp.2.........RegExp.$9 分别表示匹配到的第一个分组至第九个分组的内容

捕获组

捕获组可以通过从左到右计算其开括号来编号 。例如,在表达式 (A)(B(C)) 中,存在四个这样的组:

0(A) 、 (B(C))
1(A)
2(B(C))
3(C)

懂了捕获组,可以再了解反向引用

反向引用

对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 \n 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。

也就是说在后面的表达式中,可以使用组的编号来引用前面表达式所捕获到的文本序列。

比如有以下正则:

([a-z])([a-z])\2\1
则可以匹配字符串abba

([a-z])([a-z])\2\1先简化为(a)(b)\2\1,新手可以先不管\2\1。 先看(a)(b),就是匹配“ab"。(a)(b)\2\1就是“ab"加上\2\1的内容进行匹配,\2这里是(b),(a)(b)\2就是匹配"abb",同理(a)(b)\2\1匹配"abba"。

  1. 反向引用的最简单的、最有用的应用之一,是提供查找文本中两个相同的相邻单词的匹配项的能力。以下面的句子为例:
var str = "Is is the cost of of gasoline going up up";
var patt1 = /\b([a-z]+) \1\b/ig;
document.write(str.match(patt1));
> Is is,of of,up up

\1是对以前捕获的子匹配项的引用,指定第一个子匹配项。

  1. 判断一个字符串中出现次数最多的字符,并统计次数
var s = 'aaaaacccccbbbbb';

var a = s.split('');
a.sort();
s = a.join('');
var pattern = /(\w)\1*/g;                            ==>    这里\1是什么意思?如果不写这个会怎样?
var ans = s.match(pattern);
ans.sort(function(a, b) {
  return a.length < b.length;
});;
console.log(ans[0][0] + ': ' + ans[0].length);
 
有\1的情况下ans的值为:
["aaaaa","bbbbb","ccccc"]  
 
没有\1的情况下ans的值为:
["aaaaabbbbbccccc"]
 
如果是\2或者\3呢?
ans值为:["a","a","a","a","a","b","b","b","b","b","c","c","c","c","c"]

正则表达式中的小括号"()"。是代表分组的意思。 如果再其后面出现\1则是代表与第一个小括号中要匹配的内容相同。

注意:\1必须与小括号配合使用

  1. 将通用资源指示符 (URI) 分解为其组件。假定您想将下面的 URI 分解为协议(ftp、http 等等)、域地址和页/路径
var str = "http://www.runoob.com:80/html/html-tutorial.html";
var patt1 = /(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)/;
arr = str.match(patt1);
for (var i = 0; i < arr.length ; i++) {
    document.write(arr[i]);
    document.write("<br>");
}

第三行代码 str.match(patt1) 返回一个数组,实例中的数组包含 5 个元素,索引 0 对应的是整个字符串,索引 1 对应第一个匹配符(括号内),以此类推。
>
http://www.runoob.com:80/html/html-tutorial.html
http
www.runoob.com
:80
/html/html-tutorial.html

第一个括号子表达式包含 http 第二个括号子表达式包含 www.runoob.com,子表达式匹配非 : 和 / 之后的一个或多个字符。 第三个括号子表达式包含 :80 第四个括号子表达式包含 /html/html-tutorial.html,该子表达式能匹配不包括 # 或空格字符的任何字符序列。

非捕获组

可以使用非捕获元字符 ?:、?= 或 ?! 来重写捕获,忽略对相关匹配的保存。

  1. 以 (?) 开头的组是纯的非捕获 组,它不捕获文本 ,也不针对组合进行计数。就是说,如果小括号中以?号开头,那么这个分组就不会捕获文本,当然也不会有组的编号,因此也不存在反向引用。

通过捕获组就能得到想要匹配的内容了,那为什么还要有非捕获组呢?

原因是捕获组捕获的内容是被存储在内存中,可供以后使用,比如反向引用就是引用的内存中存储的捕获组中捕获的内容。

而非捕获组则不会捕获文本,也不会将它匹配到的内容单独分组来放到内存中。所以,使用非捕获组较使用捕获组更节省内存

1、非捕获组(?:Pattern)

它的作用就是匹配Pattern字符,好处就是不捕获文本,不将匹配到的字符存储到内存中,从而节省内存。

匹配indestry或者indestries

可以使用indestr(y|ies)或者indestr(?:y|ies)

(?:a|A)123(?:b)可以匹配a123b或者A123b

非捕获组有很多种形式,其中包括:零宽度断言和模式修正符

  1. 零宽度断言 | | |
    | :--------- | :--: | | (?= X ) | exp1(?=exp2):查找 exp2 前面的 exp1。 |
    | (?!= X ) | exp1(?!exp2):查找后面不是 exp2 的 exp1。| | (?<= X ) | (?<=exp2)exp1:查找 exp2 后面的 exp1。 | |(?<! X ) | (?<!exp2)exp1:查找前面不是 exp2 的 exp1。 |
(?=l),表示'l'字符前面的位置

var result = "hello".replace(/(?=l)/g, '#');
console.log(result); 
//  "he#l#lo"

(?!p)就是(?=p)的反面意思,比如:

var result = "hello".replace(/(?!l)/g, '#');

console.log(result); 
//  "#h#ell#o#"

(?!^) 要求匹配的到这个位置不能是开头


var result = "123456789".replace(/(?=(\d{3})+$)/g, ',')
console.log(result); 
//  ",123,456,789"


所以需要加上(?!^)  要求匹配的到这个位置不能是开头

var string1 = "12345678",
string2 = "123456789";
reg = /(?!^)(?=(\d{3})+$)/g;

var result = string1.replace(reg, ',')
console.log(result); 
//  "12,345,678"

result = string2.replace(reg, ',');
console.log(result); 
//  "123,456,789"

3、模式修正符

以(?)开头的非捕获组除了零宽度断言之外,还有模式修正符。

正则表达式中常用的模式修正符有i、g、m、s、x、e等。它们之间可以组合搭配使用。

(?imnsx-imnsx: ) 应用或禁用子表达式中指定的选项。
例如,(?i-s: ) 将打开不区分大小写并禁用单行模式。关闭不区分大小写的开关可以使用(?-i)。
有关更多信息,请参阅正则表达式选项。

(?i)ab
表示对(?i)后的所有字符都开启不区分大小写的开关。故它可以匹配ab、aB、AbAB


(?i:a)b
它表示只对a开启不区分大小写的开关。故它可以匹配ab和Ab。不能匹配aB和AB

4、(?>Pattern)等同于侵占模式

匹配成功不进行回溯,这个比较复杂,与侵占量词“+”可以通用,比如:\d++ 可以写为 (?>\d+)。

比如:将一些多位的小数截短到三位小数:

\d+\.\d\d[1-9]?\d+

在这种条件下 6.625 能进行匹配,这样做没有必要,因为它本身就是三位小数。最后一个“5”本来是给 [1-9] 匹配的,但是后面还有一个 \d+ 所以,[1-9] 由于是“?”可以不匹配所以只能放弃当前的匹配,将这个“5”送给 \d+ 去匹配,如果改为:

\d+.\d\d[1-9]?+\d+

的侵占形式,在“5”匹配到 [1-9] 时,由于是侵占式的,所以不会进行回溯,后面的 \d+ 就匹配不到任东西了,所以导致 6.625 匹配失败。

这种情况,在替换时就有效了,比如把数字截短到小数点后三位,如果正好是三位小数的,就可以不用替换了,可以提高效率,侵占量词基本上就是用来提高匹配效率的。

把 \d+.\d\d[1-9]?+\d+ 改为 \d+.\d\d(?>[1-9]?)\d+ 这样是一样的。

经典案例

  • 判断密码长度6-12位,由数字、小写字符和大写字母组成,但必须至少包括2种字符
var reg = /(?=.*[0-9])(?=.*[a-z])^[0-9A-Za-z]{6,12}$/;

(?=.*[0-9])表示该位置后面的字符匹配.*[0-9],即,有任何多个任意字符,后面再跟个数字。

翻译成大白话,就是接下来的字符,必须包含个数字。

  • 替换
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"

www.cnblogs.com/kevin-yuan/…

www.runoob.com/regexp/rege…

JS正则表达式完整教程(略长) - 掘金 (juejin.cn)

(可以参考不熟悉点: 第2、3章 包含 \b和\B 重点看案例)