8.6 使用RegExp对象
在JavaScript中,每个正则表达式都是一个对象。正则表达式是使用RegExp对象来表示的,除了RegExp()构造函数外,RegExp对象还拥有很多属性和方法,熟练掌握这些方法和属性的使用,能够提高正则表达式的应用技巧。
8.6.1 实例属性
RegExp类比较特殊,它不仅定义了类(即静态)属性,还定义了实例属性。也就是说,它既定义了属于构造函数RegExp()的全局属性,又定义了属于RegExp对象的具体实例属性。本节将讲解正则表达式的实例属性,说明如表8-8所示。
表8-8 RegExp对象的实例属性
【示例1】对于任何一个正则表达式对象来说,它都会拥有这5个属性。不过由于可以使用标志来设置正则表达式的这些属性,所以一般很少使用它们。
var r=/a/gi; //声明正则表达式直接量 alert(r.global); //返回true alert(r.ignoreCase); //返回true alert(r.multiline); //返回false alert(r.source); //返回a
注意:global、ignoreCase、multiline和source属性都是只读属性。
【示例2】lastindex属性比较有用,它是可以读写的。对于具有标志g的匹配模式来说,该属性存储了在字符串中下一次开始检索的位置。它仅与exec()和test()方法配合使用。
var s = "javascript is not java"; var r=/a/gi; //正则表达式直接量 r.exec(s); //第一次执行匹配 alert(r.lastIndex); //返回值为2 r.exec(s); //第二次执行匹配 alert(r.lastIndex); //返回值为4 r.exec(s); //第三次执行匹配 alert(r.lastIndex); //返回值为20 r.exec(s); //第四次执行匹配 alert(r.lastIndex); //返回值为22 r.exec(s); //第五次执行匹配 alert(r.lastIndex); //返回值为0
在上面示例中,正则表达式r查找字母a。当它首次检测时,发现在第二个位置(即序号为1)有一个字母a,于是lastIndex属性就被设置为2,即开始下一次匹配时的起始位置。当再次调用exec()方法时,就会从lastIndex属性指定的位置开始匹配,依此类推。
【示例3】可以手动改变lastIndex属性值,强迫正则表达式从指定的位置开始执行检测。
var s = "0123456789"; var r=/\d/g; //匹配单个数字 r.lastIndex=5; //指定匹配起始位置为5,即从第六个字符开始匹配 var a=r.exec(s); //执行匹配 alert(a); //返回匹配数字为5
8.6.2 静态属性
正则表达式的静态属性比较特殊,因为它们都有两个名字:长名(全称)和短名(简称,以美元符号开头表示),详细说明如表8-9所示。
表8-9 RegExp的静态属性
【示例1】在本示例中,匹配字符串"JavaScript",不区分大小写。
var s = "JavaScript, not Javascript"; var r = /(Java)Script/gi; var a=r.exec(s); //执行匹配操作 alert(RegExp.input); //返回字符串"JavaScript, not Javascript" alert(RegExp.leftContext); //返回空字符串,因为第一次匹配操作时,左侧没有内容 alert(RegExp.rightContext); //返回字符串", not Javascript" alert(RegExp.lastMatch); //返回字符串"JavaScript" alert(RegExp.lastParen); //返回字符串"Java"
该示例演示了正则表达式的几个静态属性的用法。
input属性实际上存储的是被执行匹配的字符串,即整个字符串"JavaScript, not Javascript"。
leftContext属性存储的是执行第一次匹配之前的子字符串,这里为空,因为在第一次匹配的文本"JavaScript"左侧为空。而rightContext属性存储的是执行第一次匹配之后的子字符串,即为", not Javascript"。
lastMatch属性包含的是第一次匹配的子字符串,即"JavaScript "。
lastParen属性包含的是第一次匹配的分组,即"Java"。
【示例2】如果模式中包含多个分组,则会显示最后一个分组所匹配的字符。
var r = /(Java)(Script)/gi; var a=r.exec(s); //执行匹配操作 alert(RegExp.lastParen); //返回字符串"Script",而不再是"Java"
可以使用短名来读取这些属性所包含的值,考虑到这些短名不符合JavaScript语法规范,则必须使用中括号运算符来进行读取操作。不过对于$_属性来说,它符合JavaScript标识符语法规范,因此可以直接使用。
【示例3】针对上面示例也可以这样设计。
var s = "JavaScript, not Javascript"; var r = /(Java)(Script)/gi; var a = r.exec(s); alert(RegExp.$_); //返回字符串"JavaScript, not Javascript" alert(RegExp["$`"]); //返回空字符串 alert(RegExp["$'"]); //返回字符串", not Javascript" alert(RegExp["$&"]); //返回字符串"JavaScript" alert(RegExp["$+"]); //返回字符串"Java"
这些属性的值都是动态的,每次执行exec()或test()方法时,所有属性值都会被重新设置。
【示例4】在本示例中,第一次执行匹配和第二次执行匹配时,这些静态属性值都会实时动态更新。
var s = "JavaScript, not Javascript"; var r=/Scrip(t)/gi; //第一次定义的匹配模式 var a=r.exec(s); //执行第一次匹配 alert(RegExp.$_); //返回字符串"JavaScript, not Javascript" alert(RegExp["$`"]); //返回字符串"Java" alert(RegExp["$'"]); //返回字符串", not Javascript" alert(RegExp["$&"]); //返回字符串"Script" alert(RegExp["$+"]); //返回字符串"t" var r=/Jav(a)/gi; //第二次定义的匹配模式 var a=r.exec(s); //执行第二次匹配 alert(RegExp.$_); //返回字符串"JavaScript, not Javascript" alert(RegExp["$`"]); //返回空字符串 alert(RegExp["$'"]); //返回字符串"Script, not Javascript" alert(RegExp["$&"]); //返回字符串"Java" alert(RegExp["$+"]); //返回字符串"a"
通过上面示例可以看出,RegExp静态属性是公共的,对于所有正则表达式对象来说都可以访问。
8.6.3 案例应用
RegExp对象定义了两个用于执行模式匹配操作的方法,如表8-10所示。它们的行为与String对象的正则表达式操作方法类似。例如,RegExp对象的exec()方法与String对象的match()方法操作相似,只不过exec()是以字符串为参数的RegExp对象方法,而match()方法是以正则表达式为参数的String对象方法。在非全局模式下,它们的返回值是相同的。
表8-10 RegExp对象的方法
1.exec()方法
在所有RegExp模式匹配方法和String模式匹配方法中,exec()方法的功能最强大。作为正则表达式的通用匹配方法,比RegExp.test()、String.search()、String.replace()和String.match()都复杂。该方法需要一个参数,用来执行要执行操作的字符串,并返回一个数组,数组中存放的是匹配结果。如果没有找到匹配,返回值为null。
var s = "javascript"; var r = /java/g; var a=r.exec(s); //返回数组["java"]
exec()方法的工作机制是这样的:当调用方法时,先检索字符串参数,从中获取与正则表达式相匹配的文本。如果找到了匹配的文本,就会返回一个数组;否则,返回null。返回数组的元素具体说明如下。
第0个元素,是与表达式相匹配的文本。
第一个元素,是与正则表达式的第一个子表达式相匹配的文本(如果存在)。
第二个元素,是与正则表达式的第二个子表达式相匹配的文本,依此类推。
返回数组还包含几个属性,具体说明如下。
length:该属性声明的是数组中的元素个数。
index:该属性声明的是匹配文本的第一个字符的位置。
input:该属性包含的就是整个字符串。
当调用非全局模式的正则表达式对象的exec()方法时,返回的数组与调用字符串对象的match()方法返回数组是完全相同的。即它们都将进行检索,并返回上述元素和属性。
当执行全局匹配模式时,exec()的行为就略有变化。这时它定义lastlndex属性,用来指定下一次执行匹配时开始检索字符串的位置。当它找到了与表达式相匹配的文本时,在匹配之后,它将把正则表达式的lastlndex属性设置为下一次匹配执行的第一个字符的位置。这就是说,可以通过反复地调用exec()方法来遍历字符串中的所有匹配文本。当exec()再也找不到匹配的文本时,它将返回null,并且把属性lastlndex重置为0。
【示例1】在本示例中,定义正则表达式直接量,用来匹配字符串s中每个字符。在循环结构的条件表达式中反复执行匹配模式,并根据返回结果的值是否为null作为循环条件。当返回值为null时,说明字符串检测完毕。然后,读取返回数组a中包含的匹配子字符串,并调用该数组的属性index和lastIndex,其中index显示当前匹配子字符串的起始位置,而lastIndex属性显示下一次匹配操作的起始位置。
var s="javascript"; //测试使用的字符串直接量 var r=/\w/g; //匹配模式 while((a=r.exec(s))! =null){ //循环执行匹配操作 alert(a[0]+"\n"+a.index +"\n"+ r.lastIndex);//显示每次匹配操作是返回的结果数组信息 }
实际上通过循环结构反复调用exec()方法是唯一获得全局模式的完整模式匹配信息的方法。
提示:无论正则表达式是否是全局模式,exec()方法都会将完整的细节添加到返回数组中。字符串对象的match()方法就不同,它在全局模式下返回的数组中不会包含这么多的细节信息。
2.test()方法
test()方法要比exec()方法简单,它能够检测参数字符串是否包含至少一个与当前正则表达式的匹配,如果包含匹配就返回true,否则就返回false。
【示例2】在下面示例中,方法test()就会返回true,因为在字符串s中包含很多个匹配:
var s = "javascript"; var r=/\w/g; //匹配字符 var b=r.test(s); //返回true
同样使用下面正则表达式也能够匹配,并返回true:
var r = /javascript/g; var b=r.test(s); //返回true
但是如果使用下面这个正则表达式进行匹配,就会返回false,因为在字符串"javascript"中就找不到对应的匹配:
var r=/\d/g; //匹配数字 var b=r.test(s); //返回false
调用test()方法等价于调用exec()方法。如果exec()方法返回值不是null,则test()方法将返回true。因此,当一个全局正则表达式调用方法test()时,它的行为与exec()方法相同,即它将从lastIndex属性值指定的位置开始验证字符串,如果发现了匹配,就会将lastIndex属性值设置为紧邻当前匹配字符串后的字符位置。因此,就可以使用test()方法代替exec()方法来遍历字符串,检测匹配的字符。
【示例3】针对exec()方法中的循环遍历匹配,可以这样设计:
var s="javascript"; //测试使用的字符串直接量 var r=/\w/g; //匹配模式 while(r.test(s)){ //循环执行匹配验证,如果返回true,则连续执行验证 alert(RegExp.lastMatch+"\n"+r.lastIndex); //利用静态属性和实例属性,显示当前匹配信息 }
【拓展】String对象定义了search()、replace()和match()方法,用来支撑正则表达式的模式匹配操作,而RegExp对象支持test()和exec()方法实现模式匹配操作。
对于search()、replace()和match()方法来说,它们返回的匹配信息没有test()和exec()方法详细。例如,test()和exec()方法支持属性lastIndex。而字符串对象的方法只是将lastIndex属性重置为0。如果使用一个全局模式的exec()方法或test()方法来检索多个字符串,那么就必须找到每个字符串中的所有匹配,以便lastIndex属性会被自动重置为0,或者必须明确地将lastIndex属性设置为0。否则,当再次检索一个新字符串时,起始位置可能就是原来的字符串中的一个任意位置,而不一定是字符串的开头。
对于test()和exec()方法来说,只有声明了g标志的正则表达式,才会发生这种特殊的lastIndex属性行为。如果正则表达式对象没有标志g, exec()和test()将忽略它的lastIndex属性。下面列表比较字符串对象和正则表达式包含的6种模式匹配的方法,如表8-11所示。
表8-11 比较各种模式匹配的方法