JavaScript与正则表达式

先介绍正则表达式在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' ]  ,数组中的顺序是整体匹配串,子匹配串,从左到右,从外到内


String也有四个可以使用正则对象的方法: match,search,replace,split

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

split:str.split(separator,[limit]); 按照正则的方式分割字符串

'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', '*' ]

replace:按照某种模式替换字符串,第一个参数是正则对象,第二个参数可以是被替换的普通字符串,可以使用$符号替换字符串,可以是函数

'aaa'.replace('a','b');  //baa,用b替换a一次
'aaa'.replace(/a/g,'b'); //bbb,用b替换a,全局替换


$& 匹配的子字符串
$`匹配结果前面的文本
$'匹配结果后面的文本
$n匹配的第n个子字符串

'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


replace第二个参数是函数,函数的第一个参数是每次匹配的全部文本($&),中间的参数是子表达式匹配的字符串,倒数第二个参数是匹配文本字符串开始匹配的下标,最后一个参数是原匹配文本,这于正则匹配结果的index和input属性是对应的。

'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)?/)  //这里?:是告诉引擎,对于组,不需要提供匹配值向后引用。


8.向后查看和向前查看(环视)

否定式向前查看: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运行情况就知道。

用一种精确的匹配方式:.好匹配除 \r\n以为的字符,那么用取反字符集来代替
'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来引用这个组匹配字符,就可以达到效果。















你可能感兴趣的:(js工作笔记)