学习《JS高级程序设计》(5)——正则

来源:互联网 发布:mac顶部工具栏 编辑:程序博客网 时间:2024/05/16 17:33

RegExp

ECMAScript通过RegExp类型来支持正则表达式。使用下面的语法,就可以创建一个正则表达式。
var expression = / pattern / flags;
其中的类型(pattern)部分可以是任何简单或复杂的正则表达式,可以包含字符类,限定符、分组、向前查找以及反向引用。每个正则表达式都可待遇一或多个标志(flags),用以标明正则表达式的行为。正则表达式的匹配模式支持下列3个标志。
1. g:表达(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止
2. i:表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写;
3. m:表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。
因此,一个正则表达式就是一个模式与上述3个标志的组合体。不同组合产生不同效果,如下面的例子所示。

/* *匹配字符串中所有“at”的实例 */var pattern1 = /at/g;/* * 匹配第一个“bat”或“cat”,不区分大小写 */var pattern2 = /[bc]at/i;/* *匹配所有以“at”结尾的3个字符的组合,不区分大小写 */var pattern3 = /.at/gi;

与其他语言中的正则表达式类似,模式中使用的所有元字符都必须转义。正则表达式中的元字符包括:
{ [ { \ ^ $ | } ? * + .] }
哲学系元字符在正则表达式中都有一或多种特殊用途,因此如果想要匹配字符串中包含的这些字,就必须对它们进行转义。下面给出几个例子。

/* * 匹配第一个“bat”或“cat”,不区分大小写 */var pattern1=/[bc]at/i;/* * 匹配第一个“[bc]at”,不区分大小写 */var pattern2=/\[bc\]at/i;/* * 匹配所有以“at”结尾的3个字符的组合,不区分大小写 */var pattern3=/.at/gi;/* * 匹配所有的“.at”,不区分大小写 */var pattern4=/\.at/gi;

在上面的例子中,pattern1匹配第一个“bat”或“cat”。而要想直接匹配“[bc]at”的话,就需要像定义pattern2亿元,对其中的两个方括号进行转义。对于pattern3来说,据点表示位于“at”之前的任意一个可以构成匹配项的字符。但如果想匹配“.at”,则必须对句点本身进行转义,如pattern4所示。
前面举打打这些例子都是以字面量形式来定义的正则表达式。另一种创建正则表达式的方式是使用RegExp构造函数,它接收两个参数:一个是要匹配的字符串模式,另一个是可选的标志字符串。可以使用字面量定义的任何表达式,都可以使用构造函数来定义,如下面的例子所示。

/* * 匹配第一个“bat”或“cat”,不区分大小写 */var pattern1=/[bc]at/i;/* * 与pattern1相同,只不过是使用构造函数创建的 */var pattern2=new RegExp("[bc]at","i");

在此,pattern1和pattern2是两个完全等价的正则表达式。要注意的是,传递给RegExp构造函数的两个参数都是字符串。由于RegExp构造函数的模式参数是字符串,所以在某些情况下要对字符进行双重转义。所有元字符都必须双重转义,那些已经转义过的字符也是如此,例如\n(字符\在字符串中通常被转义为\,而在正则表达式字符串中就会变成\\)。下表给出了一些模式,左边是这些模式的字面量形式,右边是使用RegExp构造函数定义相同模式时使用的字符串。

字面量模式等价的字符串
/\[bc\]at/
"\\[bc\\]at"
/\.at/
"\\.at"
/name\/age/
"name\\/age"
/\d.\d{1,2}/
"\\d.\\d{1,2}"
/\w\\hello\\123/
"\\w\\\\hello\\\\123"

使用正则表达式字面量和使用RegExp构造函数创建的正则表达式不一样。在ECMAScript3中,正则表达式字面量始终会共享同一个RegExp实例,而使用构造函数创建的每一个新RegExp实例都是一个新实例。来看下面的例子。

var re = null,    i;    for (i=0;i<10;i++){        re=/cat/g;        console.log(re.test("catastrophe"));//true*10    }    for(i=0;i<10;i++){        re = new RegExp("cat","g");        console.log(re.test("catastrophe"));//true*10}

第二个循环使用RegExp构造函数在每次循环中创建正则表达式。因为每次迭代都会创建一个新的RegExp实例,所以每次调用test()都会返回true;
第一个循环中,使用正则表达式字面量和直接调用RegExp构造函数一样,每次都创建新的RegExp实例,所以每次调用test()都会返回true。

RegExp实例属性

RegExp的每个实例都具有下列属性,通过这些属性可以取得有关模式的各种信息。

1.global:布尔值,表示是否设置了g标志2.ignoreCase:布尔值,表示是否设置了i标志。3.lastIndex:整数,表示开始搜索下一个匹配项的字符位置,从0算起。4.multiline:布尔值,表示是否设置了m标志。5.source:正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。

通过这些属性可以获知一个正则表达式的各方面信息,但却没有多大用处,因为这些信息全都包含在模式声明中。例如:

var pattern1=/\[bc\]at/i;alert(pattern1.global); //false;alert(pattern1.ignoreCase); //true;alert(pattern1.multiline); //false;alert(pattern1.lastIndex); //0;alert(pattern1.source); //"\[bc\]at";var pattern2=new RegExp("\\[bc\\]at","i");alert(pattern2.global); //false;alert(pattern2.ignoreCase); //true;alert(pattern2.multiline); //false;alert(pattern2.lastIndex); //0;alert(pattern2.source); //"\[bc\]at";

我们注意到,尽管第一个模式使用的是字面量,第二个模式使用了RegExp构造函数,但它们的source属性是相同的。可见,source属性保存的是规范形式的字符串,即字面量形式所用的字符串。

RegExp实例方法

exec

RegExp对象的主要方法是exec(),该方法是专门为捕获组而设计的。exec()接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配项的情况下返回null。返回的数组虽然是Array的实例,但包含两个额外的属性:index和input。其中,index表示匹配项在字符串的位置,而input表示应用正则表达式的字符串。在数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串(如果模式中没有捕获组,则该数组只包含一项)。
请看下面的例子。

var text="mom and dad and baby";var pattern = /mom( and dad( and baby)?)/gi;var matches = pattern.exec(text);alert(matches.index); //0alert(matches.input); //"mom and dad and baby"alert(matches[0]);   //"mom and dad and baby"alert(matches[1]);   //" and dad and baby"alert(matches[2]);   // " and baby"

这个例子中的模式包含两个捕获组。最内部的捕获组匹配“and baby”,而包含它的捕获组匹配“and dad”或者”and dad and baby”。当把字符串传入exec()方法中之后,发现了一个匹配项。因为整个字符串本身与模式匹配,所以返回的数组matches的index属性值为0。数组中的第一项是匹配的整个字符串,第二项包含与第一个捕获组匹配的内容,第三项包含与第二个捕获组匹配的内容。
对于exec()方法而言,即使在模块中设置了全局标志(g),它每次也只会返回一个匹配项。在不设置全局标志的情况下,在同一个字符串上多次调用exec()将始终返回第一个匹配项的信息。而在设置全局标志的情况下,每次调用exec()则都会在字符串中继续查找新匹配项,如下面的例子所示。

var text = "cat, bat, sat, fat";var pattern1=/.at/;var matches=pattern1.exec(text);alert(matches.index); //0alert(matches[0]);//catalert(pattern1.lastIndex);//0matches = pattern1.exec(text);alert(matches.index);//0alert(matches[0]);//catalert(pattern1.lastIndex);//0var pattern2=/.at/g;var matches = pattern2.exec(text);alert(matches.index);alert(matches[0]);//catalert(pattern2.lastIndex);//3matches=pattern2.exec(text);alert(matches.index);//5alert(matches[0]);//batalert(pattern2.lastIndex);//8

这个例子的第一个模式pattern1不是全局模式,因此每次调用exec()返回的都是第一个匹配项(”cat”)。而第二个模式pattern2是全局模式,因此每次调用exec()都会返回字符串的下一个匹配项,直至搜索到字符串末尾位置。此外,还应该注意模式的lastIndex属性的变化情况。在全局匹配模式下,lastIndex的值在每次调用exec()后都会增加,而在非全局模式下则始终保持不变。

test

正则表达式的第二个方法是test(),它接受一个字符串参数。在模式与该参数匹配的情况下返回true;否则,返回false。在只想知道目标字符串与某个模式是否匹配,但不需要知道其文本内容的情况下,使用这个方法非常方便。因此test()方法经常被用在if语句中,如下面的例子所示

var text="000-00-0000";var pattern=/\d{3}-\d{2}-\d{4}/;if(pattern.test(text)){    alert("The Pattern was matched.");}

在这个例子中,我们使用正则表达式来测试了一个数字序列。如果输入的文本与模式匹配,则显示一条消息。这种用法经常出现在验证用户输入的情况下,因为我们只想知道输入是否有效,至于它为什么有效就无关紧要了。
RegExp实例继承的toLocalaString()和toString()方法都会返回正则表达式的字面量,与创建正则表达式的方式无关。例如:

var pattern = new RegExp("\\[bc\\]at","gi");alert(pattern.toString());// /\[bc\]at/gialert(pattern.toLocaleString()); // /\[bc\]at/gialert(pattern.valueof());// /\[bc\]at/gi

即使上例的模式时通过调用RegExp构造函数创建的,但toLocaleString()和toString()方法仍然会像它是以字面量形式创建的一样显示其字符串表示。

compile()

compile() 方法用于在脚本执行过程中编译正则表达式。
compile() 方法也可用于改变和重新编译正则表达式。
语法
RegExpObject.compile(regexp,modifier)

参数 描述 regexp 正则表达式。 modifier 规定匹配的类型。”g” 用于全局匹配,”i” 用于区分大小写,”gi” 用于全局区分大小写的匹配。

例子如下
在字符串中全局搜索 “man”,并用 “person” 替换。然后通过 compile() 方法,改变正则表达式,用 “person” 替换 “man” 或 “woman”,且不区分大小写:

var str="Every man in the world! Every woMan on earth!";patt=/man/g;str2=str.replace(patt,"person");document.write(str2+"<br />");patt.compile("(wo)?man" ,"gi");str2=str.replace(patt,"person");document.write(str2);

输出

Every person in the world! Every woperson on earth!Every person in the world! Every person on earth!

RegExp构造函数属性

RegExp构造函数包含一些属性(这些属性在其他语言中被看成是静态属性)。这些属性适用于作用域中的所有正则表达式,并且基于所执行的最近一次正则表达式操作而变化。关于这些属性的另一个独特之处,是可以通过两种方式访问它们。换句话说,这些属性分别有一个长属性名和一个短属性名。下表列出了RegExp构造函数的属性。

长属性名 短属性名 说明 input $_ 最近一次要匹配的字符串 lastMatch $& 最近一次的匹配项 lastParen $+ 最近一次匹配的捕获组 leftContext $` input字符串中lastMatch之前的文本 rightContext $’ Input字符串中lastMatch之后的文本

使用这些属性可以从exec()或test()执行的操作中提取出更具体的信息。请看下面的例子。

var text="this has been a short summer";var pattern = /(.)hort/g;if(pattern.test(text)){    alert(RegExp.input); //this has been a short summer    alert(RegExp.leftContext);//this has been a    alert(RegExp.rightContext);// summer    alert(RegExp.lastMatch); //short    alert(RegExp.lastParen);//s}

以上代码创建了一个模式,匹配任何一个字符后跟hort,而且把第一个字符放在了一个捕获组中,RegExp构造函数的各个属性返回了下列值:
1. input属性返回了原始字符串;
2. leftContext属性返回了单词short之前的字符串,而rightContext属性则返回了short之后的字符串
3. lastMatch属性返回最近一次与整个正则表达式匹配的字符串,即short
4. lastParen属性返回最近一次匹配的捕获组,即例子中的s。
如上所述,例子使用的长属性名都可以用相应的短属性名来代替。只不过,由于这些短属性名大都不会有效的ECMAScript标识符,因此必须通过方括号语法来访问它们,如下所示

var text="this has been a short summer";var pattern = /(.)hort/g;if(pattern.test(text)){    alert(RegExp.$_); //this has been a short summer    alert(RegExp["$`"]);//this has been a    alert(RegExp["$'"]);// summer    alert(RegExp["$&"]); //short    alert(RegExp["$+"]);//s}

除了上面介绍的几个属性之外,还有多达9个用于存储捕获组的构造函数属性。访问这些属性的语法是RegExp.$1、RegExp.$2···RegExp.$9,分别用于存储第一、第二······第九个匹配的捕获组。在调用exec()或test()方法时,这些属性会被自动填充。然后,我们就可以像下面这样来使用它们。

var text="this has been a short summer";var pattern = /(..)or(.)/g;if(pattern.test(text)){    alert(RegExp.$1);//sh    alert(RegExp.$2);//t}

这里创建了一个包含两个捕获组的模式,并用该模式测试了一个字符串。即使test()方法只返回一个布尔值,但RegExp构造函数的属性$1和$2也会被匹配相应捕获组的字符串自动填充。

字符串的模式匹配

String 类型定义了几个用于在字符串中匹配模式的方法。

match

第一个方法就是match(),在字符串上调用这个方法,本质上与调用RegExp的exec()方法相同。match()方法只接受一个参数,要么是一个正则表达式,要么 是一个RegExp对象。来看下面的例子

var text="cat,bat,sat,fat";var pattern=/.at/;//与pattern.exec(text)相同var matches = text.match(pattern);//相当于pattern.exec(text);alert(matches.index); //0alert(matches[0]);//"cat"alert(pattern.lastIndex);//0

本例中的match()方法返回了一个数组;如果是调用RegExp对象的exec()方法并传递本例中的字符串作为参数,那么也会得到与此相同的数组:数组的第一项是与整个模式匹配的字符串,之后的每一项(如果有)保存着与正则表达式中的捕获组匹配的字符串。

另一个用于查找模式的方法是search()。这个方法的唯一参数与match()方法的参数相同:由字符串或RegExp对象指定的一个正则表达式。search()方法返回字符串中第一个匹配项的索引;如果没有找到匹配项,则返回-1。而且,search()方法始终是从字符串开头向后查找模式。看下面的例子

var text="cat,bat,sat,fat";var pos = text.search(/at/);alert(pos); //1

这个例子中的search()方法返回1,即”at”在字符串中第一次出现的位置。

replace

为了简化替换子字符串的操作,ECMASCRIPT提供了replace()方法。这个方法接受两个参数:第一个参数可以是一个RegExp对象或者一个字符串(这个字符串不会被转换成正则表达式),第二个参数可以是一个字符串或者一个函数。如果第一个参数是字符串,那么只会替换第一个子字符串。要想替换所有子字符串,唯一的办法是提供一个正则表达式,并且要指定全局(g)标志,如下所示:

var text="cat,bat,sat,fat";var result=text.replace("at","ond");alert(result); //"cond,bat,sat,fat"result=text.replace(/at/g,"ond");alert(result); //"cond,bond,sond,fond"

在这个例子中,首先传入replace()方法的是字符串“at”和替换用的字符串“ond”。替换的结果是把“cat”变成了“cond”,但字符串中的其他字符并没有受到影响。然后,通过该将第一个参数修改为带有全局标志的正则表达式,就将全部“at”都替换成了“ond”。
如果第二个参数是字符串,那么还可以使用一些特殊的字符序列,将正则表达式操作得到的值插入到结果字符串中。下表列出了ECMAScript提供的这些特殊的字符序列

字符序列 替换文本 $$ $ $& 匹配整个模式的子字符串。与RegExp.lastMatch的值相同 $’ 匹配的子字符串之前的子字符串。与RegExp.leftContext的值相同 $` 匹配的子字符串之后的子字符串。与RegExp.rightContext的值相同 $n 匹配n个捕获组的子字符串,其中n等于0~9。例如,$1是匹配第一个捕获组的子字符串,$2是匹配第二个捕获组的子字符串,以此类推。如果正则表达式中没有定义捕获组,则使用空字符串 nn 匹配nn个捕获组的子字符串,其中nn等于01~99。例如,$01是匹配第一个捕获组的子字符串,$02是匹配第二个捕获组的子字符串,以此类推。如果正则表达式中没有定义捕获组,则使用空字符串

通过这些特殊的字符序列,可以使用最近一次匹配结果中的内容,如下面的例子所示。

var text="cat,bat,sat,fat";result=text.replace(/(.at)/g,"word($1)");alert(result);//word(cat),word(bat),word(sat),word(fat)

在此,每个以“at”结尾的单词都被替换了,替换结果是“word”后跟一对圆括号,而圆括号中是被字符序列$1所替换的单词。
replace()方法的第二个参数也可以是一个函数。在只有一个匹配(即与模式匹配的字符串)的情况下,会向这个函数传递3个参数:模式的匹配项、模式匹配项在字符串中的位置和原始字符串。在正则表达式中定义了多个捕获组的情况下,传递给函数的参数依次是模式的匹配项、第一个捕获组的匹配项、第二个捕获组的匹配项······,但最后两个参数仍然分别是模式的匹配项在字符串中的位置和原始字符串。这个函数应该返回一个字符串,表示应该被替换的匹配项。使用函数作为replace()方法的第二个参数可以实现更加精细的替换操作。请看下面这个例子。

function htmlEscape(text){    return text.replace(/[<>"&]/g),function(match,pos,originalText){    switch(match){        case "<":            return "&lt;";        case ">":            return "&gt;";        case "&":            return "&amp";        case "\"":            return "&quot;";    }}}alert(htmlEscape("<p class=\"greeting\">Hello World!</p>"));//&lt;p class=&quot;greeting&quot;&gt;Hello World!&lt;/p&gt

这里,我们为插入HTML代码定义了函数htmlEscape(),这个函数能够转义4个字符:小于号,大于号,和号以及双引号。实现这种转义的最简单方式,就是使用正则表达式查找这几个字符,然后定义一个能够针对每个匹配的字符返回特定HTML实体的函数。

split

最后一个与模式匹配有关的方法是split(),这个方法可以基于指定的分隔符将一个字符串分隔成多个子字符串,并将结果放在一个数组中。
分隔符可以是字符串,也可以是一个RegExp对象(这个方法不会将字符串看成正则表达式)。split()方法可以接受可选的第二个参数,用于指定数组的大小,以便确保返回的数组不会超过既定大小。请看下面的例子。

var colorText = "red,blue,green,yellow";var colors1=colorText.split(",");//["red","blue","green,"yellow"]var colors2=colortext.split(",",2);//["red","blue"]var colors3=colorText.split(/[^\,]+/);//["", ",", ",",",", ""]

在这个例子中,colorText是逗号分隔的颜色字符串。基于该字符串调用split(“,”)会得到一个包含其中颜色名的数组,用于分隔字符串的分隔符是逗号。为了将数组截断,让它只包含两项,以为split()方法传递第二个参数2。最后,通过使用正则表达式,还可以取得包含逗号字符的数组。需要注意的是,在最后一次调用split()返回的数组中,第一项和最后一项是两个空字符串。之所以这样,是因为通过正则表达式指定的分隔符出现在了字符串的开头(即子字符串“red”)和末尾(即子字符串“yellow”)。

例子

解析uri https://www.payfor.com:8888/z/y/x?a=012&b=91#end
将之存到相应变量中

function RegExpTest(){ var src="https://www.payfor.com:8888/z/y/x?a=012&b=9#end"; var scheme;var host;var port;var path;var queries={};var queriesContent;var hash;var re=/([\w]+):\/\/([\w.]+):([\d]+)([^?]+)\?([^#]+)#([\w]+)/g;var matches=re.exec(src);scheme=RegExp.$1;host=RegExp.$2;port=RegExp.$3;path=RegExp.$4;queriesContent=RegExp.$5;hash=RegExp.$6;var re2=/&?([\w]+)=([\w]+)/g;var matcheQuery;while((matches=re2.exec(queriesContent))!=null){    console.log(queries);    queries[RegExp.$1]=RegExp.$2;}console.log(scheme+" "+host+" "+port+" "+path+" "+queriesContent+" "+hash);}window.onload = RegExpTest(); 

while((matches=re2.exec(queriesContent))!=null){
console.log(queries);
queries[RegExp.1]=RegExp.2;
}
这里有个小坑
我开始尝试
while((matches=re2.exec(queriesContent))!=null){
console.log(queries);
queries[RegExp.1]=RegExp.2;
queryContent=RegExp.rightContext;
}
结果第二次循环就跳出
这是因为re2有一个实例属性lastIndex,正则从lastIndex开始继续匹配
,这里第一次循环后lastIndex=5,但是queryContent=”&b=9”,从第5位开始匹配queryContent匹配,当然返回null。
实际上不需要重新赋值,当定义正则表达式为全局时,一直调用exec会一直向后匹配。

阅读全文
0 0
原创粉丝点击