1

缘起

正则表达式就是一把利器,拿出来的时候往往无往不利, 但是我们常常却将之束之高阁, 只因她不是那么漂亮,不那么让人印象深刻.

基础知识

常用元字符

标识 说明
. 匹配除换行符以外的任意字符
\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']

matchexec对比

相似点:
match和exec在匹配成功时返回的都是数组,在没有匹配上时返回的都是null

不同点
1.全局匹配
当不使用全局匹配的时候,matchexec基本一致

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);

clipboard.png

当使用全局匹配的时候,matchexec返回数据不同
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);

clipboard.png

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);

clipboard.png

全局匹配分组时,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);

clipboard.png

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

参考文档


pengj
270 声望6 粉丝