常见的正则使用备忘
缘起
正则表达式就是一把利器,拿出来的时候往往无往不利, 但是我们常常却将之束之高阁, 只因她不是那么漂亮,不那么让人印象深刻.
基础知识
常用元字符
标识 | 说明 |
---|---|
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线或汉字, 若仅限英文等价于[a-z0-9A-Z_] |
\s | 匹配任意的空白符,包括空格,制表符(Tab),换行符,中文全角空格等 |
\d | 匹配数字 |
\b | 匹配单词的开始或结束 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
反义字符
标识 | 说明 |
---|---|
\W | 匹配任意不是字母,数字,下划线,汉字的字符 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结束的位置 |
[^x] | 匹配除了x以外的任意字符 |
[^aeiou] | 匹配除了aeiou这几个字母以外的任意字符 |
重复
标识 | 说明 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
分支条件 |
为了解决类似下面的问题 \(?0\d{2}[) -]?\d{8}
匹配类似(010)88886666,或022-22334455,或02912345678等电话号码. 首先是一个转义字符(,它能出现0次或1次(?),然后是一个0,后面跟着2个数字(d{2}),然后是)或-或空格中的一个,它出现1次或不出现(?),最后是8个数字(d{8})
分支条件就是将能够涉及到的所有情况都通过|
列举出来, 相当于程序代码的||
, 如果前部分满足条件, 则不会在判断后部分.
0\d{2}-\d{8}|0\d{3}-\d{7}
这个表达式能匹配两种以连字号分隔的电话号码:一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0376-2233445)。\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}
匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔
分组
使用()
将满足条件的表达式隔离出来作为独立的一部分
如: (\d{1,3}\.){3}\d{1,3}
((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
将2[0-4]\d|25[0-5]|[01]?\d\d?
当作一个整体即可
一些示例
\ba\w*\b
匹配以字母a开头的单词——先是某个单词开始处(b),然后是字母a,然后是任意数量的字母或数字(\w*)
,最后是单词结束处(\b)
。
\d+
匹配1个或更多连续的数字。这里的+是和类似的元字符,不同的是匹配重复任意次(可能是0次),而+则匹配重复1次或更多次。
\b\w{6}\b
匹配刚好6个字符的单词
零宽断言
查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\b
,^
,$
那样用于指定一个位置,这个位置应该满足一定的条件(即断言).
标识 | 说明 |
---|---|
(?=exp) | 匹配exp前面的位置 |
(?<=exp) | 匹配exp后面的位置 |
(?!exp) | 匹配后面跟的不是exp的位置 |
(?<!exp) | 匹配前面不是exp的位置 |
(?=exp)
也叫零宽度正预测先行断言
,它断言自身出现的位置的后面能匹配表达式exp。比如\b\w+(?=ing\b)
,匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找I'm singing while you're dancing.时,它会匹配sing和danc。
(?<=exp)
也叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp。比如(?<=\bre)\w+\b
会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找reading a book时,它匹配ading。
(?<=\s)\d+(?=\s)
,同时使用了两种断言, 匹配以空白符间隔的数字(再次强调,不包括这些空白符)。
为了解决:\b\w*q[^u]\w*\b
匹配包含后面不是字母u的字母q的单词。但是如果多做测试(或者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像Iraq,Benq
,这个表达式就会出错。这是因为[^u]
总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的[^u]
将会匹配q后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的w*b将会匹配下一个单词,于是\b\w*q[^u]\w*\b
就能匹配整个Iraq fighting
。负向零宽断言能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,我们可以这样来解决这个问题:\b\w*q(?!u)\w*\b
。
(?!exp)
也叫零宽度负预测先行断言
, 断言此位置的后面不能匹配表达式exp, \d{3}(?!\d)
匹配三位数字,而且这三位数字的后面不能是数字;\b((?!abc)\w)+\b
匹配不包含连续字符串abc的单词。
(?<!exp)
也叫零宽度负回顾后发断言
, 断言此位置的前面不能匹配表达式exp:(?<![a-z])d{7}匹配前面不是小写字母的七位数字
一个更复杂的例子:(?<=<(\w+)>).*(?=<\/\1>)
匹配不包含属性的简单HTML标签内里的内容。(?<=<(\w+)>)
指定了这样的前缀:被尖括号括起来的单词(比如可能是),然后是.*(任意的字符串),最后是一个后缀(?=<\/\1>)
。注意后缀里的\/
,它用到了前面提过的字符转义;\1
则是一个反向引用,引用的正是捕获的第一组,前面的(w+)匹配的内容,这样如果前缀实际上是的话,后缀就是了。整个表达式匹配的是和之间的内容(再次提醒,不包括前缀和后缀本身)。
贪婪与懒惰
贪婪: 尽可能匹配最长的字符串
懒惰: 匹配满足条件的第一个字符串
懒惰限定符
标识 | 说明 |
---|---|
*? | 重复任意次,但尽可能少重复 |
+? | 重复1次或更多次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
{n,}? | 重复n次以上,但尽可能少重复 |
a.*?b
匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab
的话,它会匹配aab
(第一到第三个字符)和ab
(第四到第五个字符)
为什么第一个匹配是aab(第一到第三个字符)而不是ab(第二到第三个字符)?简单地说,因为正则表达式有另一条规则,比懒惰/贪婪规则的优先级更高:最先开始的匹配拥有最高的优先权——The match that begins earliest wins。
关键字
- match
语法: str.match(reg)
, 参数可传入字符串或者正则表达式.
关键注意正则表达式是否携带g
, 判断实现全局匹配.
如果匹配成功, 返回匹配的值, 取返回数据的[0]
元素; 如果失败,返回null
var str = "aaabbbcccdddeeefff"; strResult = str.match(/aaa(\S*)fff/); console.log(strResult); /* 返回一个类数组, 可使用Array.prototype.slice.call(strResult)转化为真正的数组 ["aaabbbcccdddeeefff", "bbbcccdddeee", index: 0, input: "aaabbbcccdddeeefff", groups: undefined] ["原字符串", "截取出来的字符串", "位置编码", "输入", "组"] */
- exec
语法:reg.exec(str)
检索字符串中指定的值。匹配成功返回一个数组,匹配失败返回null。
- test
直接用来判断是否正确, 比较简单
- compile
compile() 方法用于改变 RegExp。
compile() 既可以改变检索模式,也可以添加或删除第二个参数。
var reg=/hello/; console.log(reg.exec('hellojs'));//['hello'] reg.compile('Hello'); console.log(reg.exec('hellojs'));//null reg.compile('Hello','i'); console.log(reg.exec('hellojs'));//['hello']
match
和exec
对比
相似点:
match和exec在匹配成功时返回的都是数组,在没有匹配上时返回的都是null
不同点
1.全局匹配
当不使用全局匹配的时候,match
和exec
基本一致
var s = "aaa bbb ccc"; var reg = /\b\w+\b/;//没有g var rs_match = s.match(reg); var rs_exec = reg.exec(s); console.log("match:",rs_match); console.log("exec:",rs_exec);
当使用全局匹配的时候,match
和exec
返回数据不同match
直接以数组的形式返回匹配的所有数据exec
返回的数据的格式和未使用全局匹配一致, 但是是逐个匹配目标字符串. 返回的index
下标能够获取第几个匹配的初始位置.
var s = "aaa bbb ccc"; var reg = /\b\w+\b/g;//有g var rs_match1 = s.match(reg); var rs_match2 = s.match(reg); var rs_exec1 = reg.exec(s); var rs_exec2 = reg.exec(s); console.log("match1:",rs_match1); console.log("match2:",rs_match1); console.log("exec1:",rs_exec1); console.log("exec2:",rs_exec2);
2.分组
无全局匹配分组时,match和exec返回结果相同。
由于正则表达式采用了括号分组,所以在返回匹配结果的同时,依次返回该结果的所有分组, 如上面示例str.match(/aaa(\S*)fff/)
返回结果, 类数组的第二个元素就是分组(()
中的数据)的元素.
var s = "aaa1 bbb2 ccc3"; var reg = /\b(\w+)(\d{1})\b/;//两个分组,无g var rs_match1 = s.match(reg); var rs_match2 = s.match(reg); var rs_exec1 = reg.exec(s); var rs_exec2 = reg.exec(s); console.log("match1:",rs_match1); console.log("match2:",rs_match1); console.log("exec1:",rs_exec1); console.log("exec2:",rs_exec2);
全局匹配分组时,match和exec返回结果不同。
match会返回所有匹配到的结果;
exec会返回本次匹配到的结果,若表达式中出现分组,则会依次返回本次匹配的全部分组:
var s = "aaa1 bbb2 ccc3"; var reg = /\b(\w+)(\d{1})\b/g; var rs_match1 = s.match(reg); var rs_match2 = s.match(reg); var rs_exec1 = reg.exec(s); var rs_exec2 = reg.exec(s); var rs_exec3 = reg.exec(s); var rs_exec4 = reg.exec(s); console.log("match1:",rs_match1); console.log("match2:",rs_match1); console.log("exec1:",rs_exec1); console.log("exec2:",rs_exec2); console.log("exec3:",rs_exec3); console.log("exec4:",rs_exec4);
replace使用
正则表达式构造函数:new RegExp("pattern"[,"flags"]);
正则表达式替换变量函数:stringObj.replace(RegExp, replace Text);
//下面的例子用来获取url的两个参数,并返回urlRewrite之前的真实Url var reg=new RegExp("(http://www.qidian.com/BookReader/)(\\d+),(\\d+).aspx","gmi"); var url="http://www.qidian.com/BookReader/1017141,20361055.aspx"; //方式一,最简单常用的方式 var rep=url.replace(reg,"$1ShowBook.aspx?bookId=$2&chapterId=$3"); console.log(rep); // http://www.qidian.com/BookReader/ShowBook.aspx?bookId=1017141&chapterId=20361055 //方式二 ,采用固定参数的回调函数 var rep2=url.replace(reg,function(m,p1,p2,p3){ console.log('mmmm => ', m, p1,p2,p3) return p1+"ShowBook.aspx?bookId="+p3+"&chapterId="+p3 }); alert(rep2); //方式三,采用非固定参数的回调函数 var rep3=url.replace(reg,function(){var args=arguments; return args[1]+"ShowBook.aspx?bookId="+args[2]+"&chapterId="+args[3];}); alert(rep3); //方法四 //方式四和方法三很类似, 除了返回替换后的字符串外,还可以单独获取参数 var bookId; var chapterId; function capText() { var args=arguments; bookId=args[2]; chapterId=args[3]; return args[1]+"ShowBook.aspx?bookId="+args[2]+"&chapterId="+args[3]; } var rep4=url.replace(reg,capText); alert(rep4); alert(bookId); alert(chapterId); //使用test方法获取分组 var reg3=new RegExp("(http://www.qidian.com/BookReader/)(\\d+),(\\d+).aspx","gmi"); reg3.test("http://www.qidian.com/BookReader/1017141,20361055.aspx"); //获取三个分组 console.log(RegExp.$0) console.log(RegExp.$1); // http://www.qidian.com/BookReader/ console.log(RegExp.$2); // 1017141 console.log(RegExp.$3); // 20361055