正则表达式的模式匹配
正则表达式(regular expression)是一个描述字符模式的对象。JavaScript的RegExp类表示正则表达式,String和RegExp都定义了方法,后者使用正则表达式进行强大的模式匹配和文本检索与替换功能。JavaScript的正则表达式语法是Perl5的正则表达式语法的大型子集,所以对于有Perl编程经验的程序员来说,学习JavaScript中的正则表达式由是小菜一碟。
注
有一些Perl正则表达式语法特性并不被ECMAScript支持,这些特性包括:s (单行模式)
和x (扩展语法)标记;\a、\e、\l、\u、\L、\U、\E、\Q、\A、\Z、\z和\G转义字苻; "(?<=
”正向后行断言和“(?<!
”负向后行断言;“(?#
”注释和扩展“(?
”的语法。
正则表达式的定义
JavaScript中的正则表达式用RegExp对象表示,可以使用RegExp( )构造函数来创建RegExp对象,不过RegExp( )对象更多的是通过一种特殊的直接量语法来创建。就像通过引号包裹字符的方式来定义字符串直接量一样,正则表达式直接量定义为包含在一对斜杠(
/
)之间的字符,例如:var pattern = /s$/;
运行这段代码创建一个新的RegExp对象,并将它赋值给变量pattern。这个特殊的RegExp对象用来匹配所有以字母“s”结尾的字符串。用构造函数RegExp( )也可以定义个与之等价的正则表达式,代码如下:
var pattern = new RegExp("s$");
RegExp直接量和对象的创建
就像字符串和数字一样,程序中每个取值相同的原始类型直接量均表示相同的值, 这是显而易见的。程序运行时每次遇到对象直接量(初始化表达式)诸如
{ }
和[ ]
的时候都会创建新对象。比如,如果在循环体中写var a =[ ]
,则每次遍历都会创建一个新的空数组。正则表达式直接量则与此不同,ECMAScript 3规范规定,一个正则表达式直接量会在执行到它时转换为一个RegExp对象,同一段代码所表示正则表达式直接量的每次运算都返回同一个对象。ECMAScript 5规范则做了相反的规定,同一段代码所表示的正则表达式直接量的每次运算都返回新对象。IE—直都是按照ECMAScript 5规范实现的,多数最新版本的浏览器也开始遵循ECMAScript 5,尽管目前该标准并未全面广泛推行。
正则表达式的模式规则是由一个字符序列组成的。包括所有字母和数字在内,大多数的字符都是按照直接量仅描述待匹配的字符的。如此说来,正则表达式
/java/
可以匹配任何包含“java”子串的字符串。除此之外,正则表达式中还有其他具有特殊语义的字符,这些字符并不按照字面含义进行匹配。比如,正则表达式/s$/
包含两个字符,第一 个字符“s”按照字面含义匹配,第二个字符$是一个具有特殊语义的元字符,用以匹配字符串的结束。因此这个正则表达式可以匹配任何以“s”结束的字符串。
直接量字符
正如上文提到的,正则表达式中的所有字母和数字都是按照字面含义进行匹配的。JavaScript正则表达式语法也支持非字母的字符匹配,这些字符需要通过反斜线(
\
)作为前缀进行转义。比如,转义字符\n
用以匹配换行符。下表中列出了这些转义字符。
正则表达式中的直接量字符
字符 匹配 字母和数字字符 自身 \o NUL字符(\u0000) \t 制表符(\u0009) \n 换行符(\u000A) \v 垂直制表符(\u000B) \f 换页符(\u000C) \r 回车符(\u000D) \xnn 由十六进制数nn指定的拉丁字符,例如, \xOA
等价于\n
\uxxxx 由十六进制数xxxx对旨定的Unicode字符,例如 \u0009
等价于\t
\cX 控制字符 ^X
,例如,\cJ
等价于换行符\n
在正则表达式中,许多标点符号具有特殊含义,它们是:
^ $ . * + ? = ! : | \ / ( ) [ ] { }
在接下来,我们将学习这些符号的含义。某些符号只有在正则表达式的某些上下文中才具有某种特殊含义,在其他上下文中则被当成直接量处理。然而,如果想在正则表达式中使用这些字符的直接量进行匹配,则必须使用前缀
\
,这是一条通行规则。其他标点符号(比如@和引号)没有特殊含义,在正则表达式中按照字面含义进行匹配。如果不记得哪些标点符号需要反斜线转义,可以在每个标点符号前都加上反斜线。另外需要注意,许多字母和数字在有反斜线做前缀时也有特殊含义,所以对于想按照直接量进行匹配的字母和数字,尽量不要用反斜线对其转义。当然,要想在正则表达式中按照直接量匹配反斜线本身,则必须使用反斜线将其转义。比如,正则表达式“
/\V
”用以匹配任何包含反斜线的字符串。
字符类
将直接量字符单独放进方括号内就组成了字符类(character class)。 一个字符类可以匹配它所包含的任意字符。因此,正则表达式
/[abc]/
就和字母“a”、“b”、“c”中的 任意一个都匹配。另外,可以通过“^
”符号来定义否定字符类,它匹配所有不包含在方括号内的字符。定义否定字符类时,将一个”^
”符号作为左方括号内的第一个字符。正 则表达式/[abc]/
匹配的是“a”、“b”、“c”之外的所有字符。字符类可以使用连字符来表示字符范围。要匹配拉丁字母表中的小写字母,可以使用/[a-z]/
,要匹配拉丁字母表中任何字母和数字,则使用/[a-zA-Z0-9]/
。由于某些字符类非常常用,因此在JavaScript的正则表达式语法中,使用了这些特殊字符的转义字符来表示它们。例如,
\s
匹配的是空格符、制表符和其他Unicode空白符,\S
匹配的是非Unicode空白符的字符。下表列出了这些字符,并且总结了字符类的语法(注意,有些字符类转义字符只能匹配ASCII字符,还没有扩展到可以处理Unicode字符,但可以通过十六进制表示方法来显式定义Unicode字符类,例如,/[\u0400-\u04FF]/
用以匹配所有的Cyrillic字符译注)。
正则表达式的字符类
字符 匹配 [...] 方括号内的任意字符 [^...]
不在方括号内的任意字符 . 除换行符和其他Unicode行终止符之外的任意字符 \w 任何ASCII字符组成的单词,等价于 [a-zA-ZO-9]
\W 任何不是ASCII字符组成的单词,等价于 [^-zA-ZO-9]
\s 任何Unicode空白符 \S 任何非Unicode空白符的字符,注意\w和\S不同 \d 任何ASCII数字,等价于[0-9] \D 除了ASCII数字之外的任何字符,等价于 [^0-9]
[\b]
退格直接量(特例)
注意,在方括号之内也可以写这些特殊转义字符。比如,由于
\s
匹配所有的空白字符,\d
匹配的是所有数字,因此/[\s\d]/
就匹配任意空白符或者数字。注意,这里有一个特例。下面我们将会看到转义符\b
具有的特殊含义,当用在字符类中时,它表示的是退格符,所以要在正则表达式中按照直接量表示一个退格符,只需要使用具有一个元素的字符 类/[\b]/
。
重复
用刚刚学过的正则表达式的语法,可以把两位数描述成
/\d\d/
,四位数描述成/\d\d\d\d/
。 但到目前为止,还没有一种方法可以用来描述任意多位的数字,或者描述由三个字母和 一个数字构成的字符串。这些正则表达式语法中较为复杂的模式都提到了正则表达式中某元素的“重复出现次数”。我们在正则模式之后跟随用以指定字符重复的标记。由于某些重复种类非常常用,因此就有一些专门用于表示这种情况的特殊字符。例如,"+”用以匹配前一个模式的一个或多个副本。下表总结了这些表示重复的正则语法。
正则表达式的重复字符语法
字符 含义 {n,m} 匹配前一项至少n次,但不能超过m次 {n,} 匹配前一项n次或者更多次 {n} 匹配前一项n次 ? 匹配前一项0次或者1次,也就是说前一项是可选的,等价于{0,1} +
匹配前一项1次或多次,等价于{1,} *
匹配前一项o次或多次,等价于{0,} 这里有一些例子:
/\d{2,4}/ // 匹配2~4个数字 /\w{3}\d?/ // 精确匹配三个单词和一个可选的数字 /\s+java \s+/ // 匹配前后带有一个或多个空格的字符串"java" /[^(]*/ // 匹配一个或多个非左括号的字符
在使用“
*
和“?
”时要注意,由于这些字符可能匹配0个字符,因此它们允许什么都不匹配。例如,正则表达式/a*/
实际上与字符串“bbbb”匹配,因为这个字符串含有0个a。
非贪婪的重复
下表中列出的匹配重复字符是尽可能多地匹配,而且允许后续的正则表达式继续匹配。因此,我们称之为“贪婪的”匹配。我们同样可以使用正则表达式进行非贪婪匹配。只须在待匹配的字符后跟随一个问号即可:“
??
”、“+ ?
”、"*?
”或 “{1,5}?
”。比如,正则表达式/a+/
可以匹配一个或多个连续的字母a。当使用“aaa”作为匹配字符串时,正则表达式会匹配它的三个字符。但是/a+?
/也可以匹配一个或多个连续字母a,但它是尽可能少地匹配。我们同样将“aaa”作为匹配字符串,但后一个模式只能匹配第一个a。使用非贪婪的匹配模式所得到的结果可能和期望并不一致。考虑以下正则表达式 /a+b/,它可以匹配一个或多个a,以及一个b。当使用“aaab”作为匹配字符串时,它会 匹配整个字符串。现在再试一下非贪婪匹配的版本
/a+?b/
,它匹配尽可能少的a和一个b。当用它来匹配“aaab”时,你期望它能匹配一个a和最后一个b但实际上,这个模式却匹配了整个字符串,和该模式的贪婪匹配一模一样。这是因为正则表达式的模式匹配总是会寻找字符串中第一个可能匹配的位置。由于该匹配是从字符串的第一个字符开始的,因此在这里不考虑它的子串中更短的匹配。
选择、分组和引用
正则表达式的语法还包括指定选择项、子表达式分组和引用前一子表达式的特殊字符。字 符T用于分隔供选择的字符。例如,
/ab|cd|ef/
可以匹配字符串“ab”,也可以匹配字符串“cd”,还可以匹配字符串“ef”。/\d{3}|[a-z]{4}/
匹配的是三位数字或者四个小写字母。注意,选择项的尝试匹配次序是从左到右,直到发现了匹配项。如果左边的选择项匹配,就忽略右边的匹配项,即使它产生更好的匹配。因此,当正则表达式
/a|ab/
匹配字符串“ab”时,它只能匹配第一个字符。正则表达式中的圆括号有多种作用。一个作用是把单独的项组合成子表达式,以便可以像处理一个独立的单元那样用“
|
”、“*
”、"+
”或者“?
”等来对单元内的项进行处理。 例如,/java(script)?/
可以匹配字符串"java”,其后可以有“script”也可以没有。/(ab|cd)+|ef/
可以匹配字符串“ef”,也可以匹配字符串“ab”或“cd”的一次或多次重复。在正则表达式中,圆括号的另一个作用是在完整的模式中定义子模式。当一个正则表达式成功地和目标字符串相匹配时,可以从目标串中抽出和圆括号中的子模式相匹配的部分。例如,假定我们正在检索的模式是一个或多个小写字母后面跟随了一位或多位数字,则可以使用模式
/[a-z]+\d+/
。但假定我们真正关心的是每个匹配尾部的数字,那么如果将模式的数字部分放在括号中(/[a-z]+(\d+)/
),就可以从检索到的匹配中抽取数字了。带圆括号的表达式的另一个用途是允许在同一正则表达式的后部引用前面的子表达式。这是通过在字符“
\
”后加一位或多位数字来实现的。这个数字指定了带圆括号的子表达式在正则表达式中的位置。例如,\1
引用的是第一个带圆括号的子表达式,\3
引用的是第三个带圆括号的子表达式。注意,因为子表达式可以嵌套另一个子表达式,所以它的位置是参与计数的左括号的位置。例如,在下面的正则表达式中,嵌套的子表达式([Ss]cript)
可以用\2
来指代:/([Jj]ava([Ss]cript)?)\sis\s(fun\w*)/
对正则表达式中前一个子表达式的引用,并不是指对子表达式模式的引用,而指的是与那个模式相匹配的文本的引用。这样,引用可以用于实施一条约束,即一个字符串各个单独部分包含的是完全相同的字符。例如,下面的正则表达式匹配的就是位于单引号或双引号之内的0个或多个字符。但是,它并不要求左侧和右侧的引号匹配(即,加入的两个引号都是单引号或都是双引号):
/['"][^'"]*['"]/
如果要匹配左侧和右侧的引号,可以使用如下的引用:
/(['"])[^'"]*\1/
\1
匹配的是第一个带圆括号的子表达式所匹配的模式。在这个例子中,存在这样一条约束,那就是左侧的引号必须和右侧的引号相匹配。正则表达式不允许用双引号括起的内容中有单引号,反之亦然。不能在字符类中使用这种引用,所以下面的写法是非法的:/(['"])[^\1]*\1/
同样,在正则表达式中不用创建带数字编码的引用,也可以对子表达式进行分组。它不是以“
(
”和“)
”进行分组,而是以和“)
”来进行分组,比如,考虑下面这个模式:/([Jj]ava(?:[Ss]cript)?)\sis\s(fun\w*)/
这里,子表达式
(?:[Ss]cript)
仅仅用于分组,因此复制符号“?
”可以应用到各个分组。这种改进的圆括号并不生成引用,所以在这个正则表达式中,\2
引用了与(fun\W*)
匹配的文本。下表对正则表达式的选择、分组和引用运算符做了总结。
正则表达式的选择、分组和引用字符
字符 含义 ` ` (...)
组合,将几个项组合为一个单元,这个单元可通过“ *
”、“+
”、“?
”和“`(?:...)
只组合,把项组合到一个单元,但不记忆与该组相匹配的字符 \n
和第n个分组第一次匹配的字符相匹配,组是圆括号中的子表达式(也有可能是嵌套的),组索引是从左到右的左括号数,形式的分组不编码 指定匹配位置
正如前面所介绍的,正则表达式中的多个元素才能够匹配字符串的一个字符。例如,
\s
匹配的只是一个空白符。还有一些正则表达式的元素匹配的是字符之间的位置,而不是实际的字符。例如,\b
匹配一个单词的边界,即位于\w
(ASCII单词)字符和\W
(非ASCII单词)之间的边界,或位于一个ASCII单词与字符串的开始或结尾之间的边界(除了在字符类(方括号)中,\b
匹配退格符。)。像\b
这样的元素不匹配某个可见的字符,它们指定匹配发生的合法位置。有时我们称这些元 素为正则表达式的锚,因为它们将模式定位在搜索字符串的特定位置上。最常用的锚元素是^
,它用来匹配字符串的开始,锚元素$
用以匹配字符串的结束。例如,要匹配单词“JavaScript”,可以使用正则表达式
/^JavaScript$/
,如果想匹配 “Java”这个单词本身(不像在“JavaScript”中作为单词的前缀),可以使用正则表达式/\s\Javas/
,可以匹配前后都有空格的单词“Java”。但是这样做有两个问题,第一,如果“Java”出现在字符串的开始或者结尾,就匹配不成功,除非开始和结尾处各有一个空格。第二个问题是,当找到了与之匹配的字符串时,它返回的匹配字符串的前端和后端都有空格,这并不是我们想要的。因此我们使用单词的边界\b
来代替真正的空格符\s
进行匹配(或定位)。这样正则表达式就写成了/\bJava\b/
。元素\B
将把匹配的锚点定位在不是单词的边界之处。因此,正则表达式/\B[Ss]cript/
与“JavaScript”和“postscript”匹配,但不与“script”和“Scripting”匹配。任意正则表达式都可以作为锚点条件。如果在符号"
(?=
”和“)
”之间加入一个表达式,它就是一个先行断言,用以说明圆括号内的表达式必须正确匹配,但并不是真正意义上的匹配。比如,要匹配一种常用的程序设计语言的名字,但只在其后有冒号时才匹配,可以使用/[Jj]ava([Ss]cript)?(?=\:)/
。这个正则表达式可以匹 配"JavaScript: The Definitive Guide” 中的"JavaScript”,但是不能匹配"Java in a Nutshell”中的“Java”,因为它后面没有冒号。带有“
(?!
”的断言是负向先行断言,用以指定接下来的字符都不必匹配。例如,/Java(?! Script)([A-Z]\w*)/
可以匹配"Java”后跟随一个大写字母和任意多个ASCII单词,但Java后面不能跟随“Script”。它可以匹配"JavaBeans",但不能匹配“Javanese”;它可以匹配“JavaScript”,但不能匹配”JavaScripter”。下表总结了正则表达式中的锚。
正则表达式中的锚字符
字符 含义 A 匹配字符串的开头,在多行检索中,匹配一行的开头 $ 匹配字符串的结尾,在多行检索中,匹配一行的结尾 \b
匹配一个单词的边界,简言之,就是位于字符 \w
和\W
之间的位置,或位于字符\w
和字符串的开头或者结尾之间的位置(但需要注意,[\b]
匹配的是退格符)\B
匹配非单词边界的位置 (?=p)
零宽正向先行断言,要求接下来的字符都与p匹配,但不能包括匹配p的那些字符 (?!p)
零宽负向先行断言,要求接下来的字符不与p匹配 修饰符
正则表达式中的语法还有最后一个知识点,即正则表达式的修饰符,用以说明高级匹配模式的规则。和之前讨论的正则表达式语法不同,修饰符是放在“
/
”符号之外的,也就是说,它们不是出现在两条斜线之间,而是第二条斜线之后。JavaScript支持三个修饰符,修饰符“i
”用以说明模式匹配是不区分大小写的。修饰符“g
”说明模式匹配应该是全局的,也就是说,应该找出被检索字符串中所有的匹配。修饰符“m
”用以在多行模式中执行匹配,在这种模式下,如果待检索的字符串包含多行,那么^
和$
锚字符除了匹配整个字符串的开始和结尾之外,还能匹配每行的开始和结尾。比如正则表达式/java$/im
可以匹配“java
”也可以匹配“Java\nis fun"。这些修饰符可以任意组合,比如,要想不区分大小写匹配字符串中的第一个单词 “java” ( “Java” 或 “JAVA” 等),可以使用不区分大小写的修饰符来定义正则表达式
/\bjava\b/i
。要想匹配字符串中所有的单词,则需要添加修饰符g:/\bjava\b/gi
。正则表达式修饰符
字符 含义 i 执行不区分大小写的匹配 g 执行一个全局匹配,简言之,即找到所有的匹配,而不是在找到第一个之后就停止 m 多行匹配模式,s匹配一行的开头和字符串的开头,$匹配行的结束和字符串的结束 用于模式匹配的String方法
String支持4种使用正则表达式的方法。最简单的是search( )。它的参数是一个正则表达式,返回第一个与之匹配的子串的起始位置,如果找不到匹配的子串,它将返回-1。比如,下面的调用返回值为4:
"JavaScript".search(/script/i);
如果search( )的参数不是正则表达式,则首先会通过RegExp构造函数将它转换成正则表达式,search( )方法不支持全局检索,因为它忽略正则表达式参数中的修饰符g。
replace( )方法用以执行检索与替换操作。其中第一个参数是一个正则表达式,第二个参数是要进行替换的字符串。这个方法会对调用它的字符串进行检索,使用指定的模式来匹配。如果正则表达式中设置了修饰符g,那么源字符串中所有与模式匹配的子串都将替换成第二个参数指定的字符串;如果不带修饰符g,则只替换所匹配的第一个子串。如果replace( )的第一个参数是字符串而不是正则表达式,则replace( )将直接捜索这个字符串,而不是像search( )一样首先通过RegExp( )将它转换为正则表达式。比如,可以使用下面的方法,利用replace( )将文本中的所有javascript(不区分大小写)统一替换为“JavaScript”:
// 将所有不区分大小写的javascript都替换成大小写正确的JavaScript text.replace(/javascript/gi, "JavaScript");
但replace( )的功能远不止这些。回忆一下前文所提到的,正则表达式中使用圆括号括起来的子表达式是带有从左到右的索引编号的,而且正则表达式会记忆与每个子表达式匹配的文本。如果在替换字符串中出现了$加数字,那么replace( )将用与指定的子表达式相匹配的文本来替换这两个字符。这是一个非常有用的特性。比如,可以用它将一个 字符串中的英文引号替换为中文半角引号:
// 一段引用文本起始于引号,结束于引号 // 中间的内容区域不能包含引号 var quote = /"([^"]*)"/g;| // 用中文半角引号替换英文引号,同时要保持引号之间的内容(存储在$1中)没有被修改 text.replace(quote, ' “$1” ');
值得注意的是,replace( )方法的第二个参数可以是函数,该函数能够动态地计算替换字符串。
match( )方法是最常用的String正则表达式方法。它的唯一参数就是一个正则表达式(或通过RegExp( )构造函数将其转换为正则表达式),返回的是一个由匹配结果组成的数组。如果该正则表达式设置了修饰符g,则该方法返回的数组包含字符串中的所有匹配结果。例如:
"1 plus 2 equals 3".match(/\d+/g) // 返回["1", "2", "3"]
如果这个正则表达式没有设置修饰符g,match( )就不会进行全局检索,它只检索第一个匹配。但即使match( )执行的不是全局检索,它也返回一个数组。在这种情况下,数组的第一个元素就是匹配的字符串,余下的元素则是正则表达式中用圆括号括起来的子表达式。因此,如果match( )返回一个数组a,那么
a[0]
存放的是完整的匹配,a[1]
存放的则是与第一个用圆括号括起来的表达式相匹配的子串,以此类推。为了和方法replace( )保持一致,a[n]
存放的是$n
的内容。例如,使用如下的代码来解析一个URL:
var url = /(\w+):\/\/([\w.]+)\/(\S*)/; var text = "Visit my blog at http://www.example.com/~david"; var result = text.match(url); if (result != null) { var fullurl = result[0]; // 包含 "http://www.example.com/~david" var protocol = result[1]; // 包含 "http" var host = result[2]; // 包含 "www.example.com" var path = result[3]; // 包含 "~david"
值得注意的是,给字符串的match( )方法传入一个非全局的正则表达式,实际上和给这个正则表达式的exec( )方法传入的字符串是一模一样的,它返回的数组带有两个属性:index和input,接下来对exec( )方法的讨论中会提到:
String对象的最后一个和正则表达式相关的方法是split( )。这个方法用以将调用它的字符串拆分为一个子串组成的数组,使用的分隔符是split( )的参数,例如:
"123,456,789".split(","); // 返回["123","456","789"]
split( )方法的参数也可以是一个正则表达式,这使得split( )方法异常强大。例如,可以指定分隔符,允许两边可以留有任意多的空白符:
"1, 2, 3, 4, 5".split(/\s*,\s*/); // 返回["1","2","3","4","5"]
RegExp对象
正则表达式是通过RegExp对象来表示的。除了RegExp( )构造函数之外,RegExp对象还支持三个方法和一些属性。接下来的两节会对RegExp模式匹配方法和属性展开讲述。
RegExp( )构造函数带有两个字符串参数,其中第二个参数是可选的,RegExp( )用以创建新的RegExp对象。第一个参数包含正则表达式的主部分,也就是正则表达式直接量中两条斜线之间的文本。需要注意的是,不论是字符串直接量还是正则表达式,都使用“
\
”字符作为转义字符的前缀,因此当给RegExp( )传入一个字符串表述的正则表达式 时,必须将“\
”替换成“\\
” 。RegExp( )的第二个参数是可选的,如果提供第二个参数, 它就指定正则表达式的修饰符。不过只能传入修饰符g、i、m或者它们的组合。比如:// 全局匹配字符串中的5个数字,注意这里使用了"\\",而不是"\" var zipcode = new RegExp("\\d{5}”, "g");
RegExp( )构造函数非常有用,特别是在需要动态创建正则表达式的时候,这种情况往往没办法通过写死在代码中的正则表达式直接量来实现。例如,如果待检索的字符串是由用户输入的,就必须使用RegExp( )构造函数,在程序运行时创建正则表达式。(其实通过eval( )也可以实现运行时动态创建正则表达式,但不推荐使用eval( ))
RegExp的属性
每个RegExp对象都包含5个属性。属性source是一个只读的字符串,包含正则表达式的文本。属性global是一个只读的布尔值,用以说明这个正则表达式是否带有修饰符g。属性ignoreCase也是一个只读的布尔值,用以说明正则表达式是否带有修饰符i。属性multiline是一个只读的布尔值,用以说明正则表达式是否带有修饰符m。最后一个属性lastindex,它是一个可读/写的整数。如果匹配模式带有g修饰符,这个属性存储在整个字符串中下一次检索的开始位置,这个属性会被exec( )和test( )方法用到,下面会讲到。
RegExp的方法
RegExp对象定义了两个用于执行模式匹配操作的方法。它们的行为和上文介绍过的String方法很类似。RegExp最主要的执行模式匹配的方法是exec( ),它与String方法match( )相似,只是RegExp方法的参数是一个字符串,而String方法的参数是一个RegExp对象。exec( )方法对一个指定的字符串执行一个正则表达式,简言之, 就是在一个字符串中执行匹配检索。如果它没有找到任何匹配,它就返回null,但如果它找到了一个匹配,它将返回一个数组,就像match( )方法为非全局检索返回的数组一样。这个数组的第一个元素包含的是与正则表达式相匹配的字符串,余下的元素是与圆括号内的子表达式相匹配的子串。属性index包含了发生匹配的字符位置,属性input引用的是正在检索的字符串。
和match( )方法不同,不管正则表达式是否具有全局修饰符g,exec( )都会返回一样的数组。回忆一下,当match( )的参数是一个全局正则表达式时,它返回由匹配结果组成的数组。相比之下,exec( )总是返回一个匹配结果,并提供关于本次匹配的完整信息。当调用exec( )的正则表达式对象具有修饰符g时,它将把当前正则表达式对象的lastindex属性设置为紧挨着匹配子串的字符位置。当同一个正则表达式第二次调用exec( )时,它将从lastIndex属性所指示的字符处开始检索。如果exec( )没有发现任何匹配结果,它会将lastindex重置为0(在任何时候都可以将lastindex属性设置为0,每当在字符串中找最后一个匹配项后,在使用这个RegExp对象开始新的字符串査找之前,都应当将lastindex设置为0)。这种特殊的行为使我们可以在用正则表达式匹配字符串的过程中反复调用exec( ),比如:
var pattern = /Java/g; var text = "JavaScript is more fun than Java!"; var result; while((result = pattern.exec(text)) != null) { alert("Matched '" + result[0] + "'" + "at position " + result.index + "; next search begins at " + pattern.lastIndex); }
另外一个RegExp方法是test( ),它比exec( )更简单一些。它的参数是一个字符串,用test( )对某个字符串进行检测,如果包含正则表达式的一个匹配结果,则返回true:
var pattern = /java/i; pattern.test("JavaScript"); // 返回 true
调用test( )和调用exec( )等价,当exec( )的返回结果不是null时,test( )返回true。由于这种等价性,当一个全局正则表达式调用方法test( )时,它的行为和exec( )相同,因为它从lastIndex指定的位置处开始检索某个字符串,如果它找到了一个匹配结果,那么它就立即设置lastIndex为当前匹配子串的结束位置。这样一来,就可以使用test( )来遍历字符串,就像用exec( )方法一样。
与exec( )和test( )不同,String方法search( )、replace( )和match( )并不会用到lastIndex属性。实际上,String方法只是简单地将lastIndex属性值重置为0。如果让一个带有修饰符g的正则表达式对多个字符串执行exec( )或test( ),要么在每个字符串中找出所有的匹配以便将lastIndex自动重置为零,要么显式将lastindex手动设置为0(当最后一次检索失败时需要手动设置lastindex)。如果忘了手动设置lastIndex的值,那么下一次对新字符串进行检索时,执行检索的起始位置可能就不是字符串的开始位置,而可能是任意位置(这里所说的任意位置实际上是由lastindex的值决定的,如果lastindex的值不为0,必定会对新开始的正则表达式匹配检索造成不确定的影响。)。当然,如果RegExp不带有修饰符g,则不必担心会发生这种情况。同样要记住,在ECMAScript 5中,正则表达式直接量的每次计算都会创建一个新的RegExp对象,每个新RegExp对象具有各自的lastIndex属性,这势必会大大减少“残留”lastlndex对程序造成的意外影响。