先介绍正则表达式在JavaScript中的使用,然后用介绍正则表达式,并用JavaScript做测试,旨对常用的正则表达式做一下记录。
本文参考来源:
深入浅出之正则表达式(一)
深入浅出之正则表达式(二)
JavaScript中正则表达式
相爱相杀——正则与浏览器间的爱恨情仇
对各字符集编码范围的总结
正则表达式有两种创建方式:字面量和RegExp对象
var r = /ab\+c/ ; //字面量创建方式
var reg = new RegExp('ab\\+c') //RegExp对象
这两种创建方式是等价的,都可以调用
test,exec方法:正则对象.test(str),正则对象.exec(str),区别在于字面量写法是在编译的时候新建正则表达式,RegExp创建对象是在运行的时候新建正则表达式;
正则对象创建后有两个属性:
lastIndex:下一次匹配开始的索引位置,使用g参数时会有用到
source:模式文本,只读属性
上面两个正则对象,初始时lastIndex=0,source=‘ab\\+c’,如果使用过之后,lastIndex会改变。
reg.lastIndex //0
reg.test('ab+c');
reg.lastIndex //4
正则匹配之后的结果,有匹配到字符串和所有被记住的子字符串,index属性,默认以0开始匹配搜索,input属性,匹配的初始字符串;以reg为例
var regArray = reg.exec('ab+caa'); // regArray :[ 'ab+c', index: 0, input: 'ab+caa' ]
var regArray = /(a)(b)/g.exec('abcdab') // regArray:[ 'ab', 'a', 'b', index: 0, input: 'abcdab' ] ,数组中的顺序是整体匹配串,子匹配串,从左到右,从外到内
match:匹配时,模式串的lastIndex无效
var s_match = '_x_x';
var r1 = /x/g;
var r11 = /x/;
var r2 = /y/;
s_match.match(r1); //['x','x'];全局模式下会是所有匹配的结果
s_match.match(r2); //null
s_match.match(r11) //['x',index:1,input:'_x_x'],
search:返回第一个满足条件的匹配结果在字符串中的位置,同时会忽略模式串中参数g的作用
'_x_y'.search('x'); //1
'_x_y'.search('y'); //3
'a ,b,c ,d'.split(','); //[ 'a ', 'b', 'c ', 'd' ]
'a, b, c,d'.split(/,\s*/) //[ 'a', 'b', 'c', 'd' ]
//如果匹配模式含有括号,则括号匹配的部分也会作为数组成员返回
'aaa*a*'.split(/(a*)/) ; [ '', 'aaa', '*', 'a', '*' ]
'aaa'.replace('a','b'); //baa,用b替换a一次
'aaa'.replace(/a/g,'b'); //bbb,用b替换a,全局替换
'abc'.replace('b','[$`-$&-$\']'); //a[a-b-c]c
"boy".replace(/\w+/g,"$&-$&"); //boy-boy
'javascript'.replace(/script/,"$& != $`"); //javascript != java,$&=script $`=java ,替代script
'javascript'.replace(/s/,"$' != $`"); // javacript != javacript
'javascript'.replace(/java/,"$&$' is "); //javascript is script
'i am a boy!'.replace(/(^|\s)([a-z])/g,function(m,p1,p2,p3,p4){
//m: i p1: p2: i p3: 0 p4: i am a boy!
return p1+p2.toUpperCase();
})
这个匹配结果是每个单词的首字母大写。
正则表达式:
1.正则表达式中有十二个特殊字符,也称作元字符
[]\^$.|?*+()
如果在模式串中匹配他们,需要用'\'进行转义。2.正则表达式中不可显示的字符(了解):
\t 表示tab
\r 表示回车符
\n 表示换行,windows中换行用\r\n,Linux中\n.Max中\r
\f 换页符
\a alert字符
\e escape字符
\b 回退字符
\v 垂直制表符
\O 空字符
3.字符集
用[ ]这种形式来表示,字符集也有四个元字符 "]\^-",分别是字符集定义的结束,转义,取反,范围定义
^:必须要在[ 后面才有取反的意义,不过他们都必须要在特定的位置。
"^x".match(/[x^]/g) //[ '^', 'x' ],这里^当做普通字符处理匹配,所以可以匹配到两个结果
"^x".match(/[^x]/g) //[ '^' ] ,取反不匹配x,所以结果只有^一个字符
-:需要在两个字符中间,同时前一个字符的ascii要小于后面的字符。
"a-x".match(/[-x]/g) //[ '-', 'x' ]
"a-x".match(/[a-x]/g) //[ 'a', 'x' ]
"a-x".match(/[x-]/g) //[ '-', 'x' ]
"a-x".match(/[x-a]/g) //报错
\和]也是类似的,只不过要注意对\和]进行转义,自己可以动手试试。
字符集中也有一些元字符:
. [^\n\r] 匹配除了换行和回车的任意字符,字母或者汉字
\d [0-9]
\w word单词 [a-zA-z_0-9]
\s 空白符,如[\t\n\f\r]
[\S] = [^\s],那么 [\s\S]就是匹配任意字符了包括回车换行,于‘.’不同。
[\W]= [^\w] 单词字符
[\D] = [^\d],[^0-9]
4.选择符 |,类似或的意思,可以匹配多个模式
"abcdx".match(/[a|x]/g) //[ 'a', 'x' ]
"abcdx".match(/[x|a]/) //[ 'a', index: 0, input: 'abcdx' ]
匹配模式中[ ]字符集要不要都可以,匹配是从匹配文本开头匹配,也是惰性的,不是全局匹配情况,匹配到一个就会返回。
5.简单量词于惰性
? 出现零次或一次
* 出现零次或多次
+ 出现一次或多次
{n} 对应0次或n次
{n,m} 至少出现n次,不超过m次
{n,} 出现n次以上
上面简单量词都是贪婪的,贪婪在于每次匹配都尽可能长的匹配,如同在找到匹配的初始位置后,一口吞掉这个文本字符,然后一点一点回溯
"example:a
b
p标签".match(/<.+>/)
//[ 'a
b
',index: 8,input: 'example:a
b
p标签' ]
这个过程是:找到<的位置后,匹配到末尾‘签’,然后一点一点回溯到第二组p标签的>
如果要匹配一组
标签,就需要用惰性匹配,在+的后面加上?,可以用惰性去掉贪婪性
"example:a
b
p标签".match(/<.+?>/)
//[ '', index: 8, input: 'example:
a
b
p标签' ]
*也是可以用?去掉贪婪性
6.单词边界和零宽度
首先需要说一下字符和位置的关系,在正则眼中,n个字符,n+1个位置,位置是可以被匹配到的,匹配位置时不消耗字符,单词边界匹配到的就是位置而不是字符,\b 匹配到的字符长度为0,匹配一个单词的开头和结尾,有一下几种情况:
1) 在字符串的第一个字符前的位置(如果字符串的第一个字符是一个“单词字符”)
2) 在字符串的最后一个字符后的位置(如果字符串的最后一个字符是一个“单词字符”)
3) 在一个“单词字符”和“非单词字符”之间,其中“非单词字符”紧跟在“单词字符”之后
4) 在一个“非单词字符”和“单词字符”之间,其中“单词字符”紧跟在“非单词字符”后面
如果一个子表达式匹配的是位置而不是字符,或者匹配到的结果不保留到最终的结果,那么这个子表达式就是零宽度,如/a(?=b)/ 匹配ab,但是只将a存入结果,(?=b)就是零宽度,本质代表一个位置。零宽度是非互斥的,位置是可以多个匹配的,比如/\b\ba/是可以匹配a的,虽然正则表达式里面有2个\b,但可以同时匹配到位置0。
7.组与向后引用
只有()才能形成组,[]定义字符集,{}定义重复操作;
\n 是第n个匹配的引用组 \0是,引用整个正则匹配的结果,一般只在替换的时候有用到,向后引用不能用于字符集内部,也就是
(a)[\1b] 是不能对a进行引用的,先看一个向后引用的例子:
'bbaxaxaxx'.match(/([a-c])x\1x\1/) //[ 'axaxa', 'a', index: 2, input: 'bbaxaxaxx' ]
这里\1是对([a-c])的引用,\1引用的结果都是相同的,在引用时,编号也是从左到右,从外到内的。
对于重复组的操作,引用只取最后一个的值:
'abc=abc'.match(/([abc]+)=\1/) //[ 'abc=abc', 'abc', index: 0, input: 'abc=abc' ]
'abc=cab'.match(/([abc])+=\1/) //[ 'abc=c', 'c', index: 0, input: 'abc=cab' ]
([abc])+ 这个组重复,引用只取最后一次匹配c
\0 引用整个组只能用作替换:
'abcababcab'.replace(/(ab)c\1/,'\0test') //testabcab
'abcababcab'.replace(/(ab)c\1/g,'\0test') //testtest
\0在替换的时候引用整个匹配的结果
向后引用会降低引擎的速度,因为需要存储匹配的组,如果不需要向后引用,可以告诉引擎某个组不匹配,?:
'GetValue'.match(/Get(?:Value)?/) //这里?:是告诉引擎,对于组,不需要提供匹配值向后引用。
否定式向前查看:q(?!xx)
肯定式向前查看:q(?=xx)
否定式向后查看的语法: (? 肯定式向后查看的语法: (?<=xx)
例:<<(?>将会匹配一个没有“a”作前导字符的“b”。
JavaScript只支持向前查看。
匹配的是:后(前)面满足(不满足)匹配规则的位置。
"hhh-123-123hh4444-3".split(/-(?=\d+$)/)
这个例子,是以'-'来分割字符串,后半段是数字,那么就需要在‘-’ 符号向前查看,是否满足数字的要求,同时前面也可能有“-”.
9.原子组和防止回溯
比如字符串:‘1,2,3,4,5,6,7,8,9,10,11,12’,用^(.*?,){11}P 取匹配的时候就会有大量的回溯,匹配失败,为了避免这种回溯,一种办法是用更加精确的匹配方式,用取反
字符集代替'.'
'1,2,3,4,5,6,7,8,9,10,11,P'.match(/^(.*?,){11}P/) //如果没有匹配成功就会大量回溯
[ '1,2,3,4,5,6,7,8,9,10,11,P',
'11,', //match到的之后保留最后一次匹配的结果
index: 0,
input: '1,2,3,4,5,6,7,8,9,10,11,P' ]
这个例子如果没有匹配成功会有大量的回溯,不过可能不是很明显,可以看一下这个例子:
var str = 'JavaScript is the best language in the world.'
var reg = '/(.+.+)+X/'
ret.test(str);
运行这个例子,打开任务管理器看cup运行情况就知道。
'1,2,3,4,5,6,7,8,9,10,11,P'.match(/^([^,\r\n]*,){11}P/)
还有一个种方式是原子组,原子组被认为是单一的正则表达式,匹配失败后整体回退到原子组前面的位置回溯,(?>正则表达式),上面的^(.*?,){11}P 可以写成
^(?>(.*?,){11})P,不过JavaScript不支持原子组。我们可以用另外一种凡是代替,用环视或者零宽度断言和向后引用组来模拟代替:
'1,2,3,4,5,6,7,8,9,10,11,P'.match(/^(?=((.*?,){11}))\1P/) //匹配结果同上
/^(?=((.*?,){11}))\1P/ 这个匹配中向前查看一个组((.*?,){11}) ,这个相当于原子组,只匹配位置不匹配字符,我们用\1来引用这个组匹配字符,就可以达到效果。