JavaScript正则表达式和模式匹配

一、概述

正则表达式是一个对象,用来描述字符串的模式。JavaScript用RegExp类表示正则表达式,String和RegExp两个类都定义了一些方法来使用正则表达式,完成一些基于文本的模式匹配、查找与替换等强大功能。JavaScript的正则表达式语法完全是Perl5正则表达式语法的一个子集。本文先介绍正则表达式的语法,然后介绍String和RegExp类使用正则表达式的一些方法。

二、定义正则表达式

在JavaScript中,正则表达式是用RegExp类表示的,所以我们当然可以使用RegExp的构造函数来定义正则表达式对象;但是就像字符串一样,我们也可以使用字面量来定义正则表达式,只不过字符串是使用一对引号包含一串字符,而正则表达式是使用一对斜杠包含("/")一串字符。

例子:使用RegExp:var pattern=new RegExp("s$");

上面的例子等价于:var pattern = /s$/; 他们都匹配一个s字符结尾的字符串。

1、字符字面量

(1)正则表达式中的字母和数字字符,都匹配他们本身。

(2)正则表达式使用反斜杠加一个特殊字符,可以表示非字母和数字的字符。有:

\n (换行),\v (vertical tab), \0 (nul字符),\f(form feed), \t(Tab), \r(回车), \uxxxx(4位16进制数字xxxx表示的unicode字符), \xnn(2位16进制数字nn表示的拉丁字符),\cX(X表示的控制字符)

(3)标点符号:^ $ . * ! + ? = : | / \ ( ) [ ] { }18个字符在正则表达式中有特殊含义,如果要匹配这些字符本身,需要在前面加个反斜杠。不过其他的标点比如@符号,引号等不需要加反斜杠就表示他们本身。

如果在匹配的时候不记得哪些需要加反斜杠,哪些不需要,一个安全的做法就是在所有需要匹配标点符号本身的情况下,都在标点符号前加上反斜杠。

2、字符组

多个单个字符合并一起放到一个中括号中,表示一个字符组,字符组匹配在字符组中的任意一个字符。如:/[abc]/ 匹配a,b,c中的任意一个。

在字符组的左括号后加上一个插入符表示反字符组,反字符组匹配不在字符组中的任意一个字符,如:/[^abc]/匹配除了a,b,c的任意字符。

字符组中可以使用连字符表示一个字符范围,如:/[a-zA-Z0-9/匹配26个大小写字母和0到9的数字中的任何一个。

由于字符组在正则表达式中经常使用,所以JavaScript在正则表达式的语法中定义了一些特殊的转义字符表示这些字符组。对字符组归纳一下:

[...]          --匹配括号中任意单个字符 

[^...]        --匹配非括号中的任意单个字符,即上面的反字符组

.               --单个句点匹配除了换行以及其他Unicode的行结束符外的任意字符

/w             --匹配任意Ascii字符,等价于[a-zA-Z0-9_]

/W            --匹配任意非Ascii字符,等价于[^a-zA-Z0-9_],即上面的反字符组

/s              --匹配任意Unicode空字符

/S              --匹配任意非Unicode空字符,即上面的反字符组

/d              --匹配任意数字,等价于[0-9]

/D              --匹配非数字,等价于[^0-9],即上面的反字符组

[\b]            --匹配单个退格字符,这个特殊情况是因为\b在正则表达式中有其他的意义。

3、重复

重复表示上一个模式可以重复的次数,重复的语法有:

{n,m}      --上一个模式至少出现n次,但是不超过m次

{n,}         --上一个模式至少出现n次,没有上限

{n}          --上一个模式只能出现n次

?              --上一个模式出现0次或者1次,等价于{0,1}

+             --上一个模式至少出现1次,等价于{1,}

*             --上一个模式出现0次或者多次,等价于{0,}

(1)非贪婪重复

上面的重复语法表示的都是贪婪重复,贪婪重复表示在匹配过程中,在模式的其他部分仍然可以匹配的情况下,尽可能多地去匹配。

我们可以在重复语法的后面加个问号,表示非贪婪匹配,非贪婪匹配是只要匹配到了,就不继续往下匹配了。

比如有字符串”aaa“,用/a+/可以匹配到整个字符串”aaa“,而用/a+?/仅仅匹配了字符串的第一个字符”a“

使用非贪婪重复,不一定能产生期望的效果,比如字符串"aaab",用/a+b/可以匹配整个字符串,如果你使用/a+?b/,可能是想匹配字符串的后两位,但是实际上该匹配返回的还是整个字符串。

4、可选、组合、引用

|   --用来分割可选的匹配模式,如/ab|cd|ef/  匹配ab,或者cd,或者ef,有多个可选方案时,采用从左到右的匹配顺序,找到匹配结果就停止,即使后面有更优的匹配。比如/a|ab/ 匹配字符串ab的时候,结果为a。

()  --圆括号表示一个组合,组合里的模式当做一个整体看待,比如/java(script)?/,匹配java或者javascript。

正则表达式中的圆括号的另一个用途就是,用作子模式。比如/[a-z]+\d+/,匹配小写字母开头,数字结尾的字符串。如果你只关心每个匹配中结尾的数字,可以使用/[a-z]+(\d+)/。

有了子模式,我们还可以通过一个反斜杠加一个数字对子模式进行引用,数字是子模式在整个模式中的索引(第几个左括号)。不过引用的不是子模式本身,而是子模式的匹配结果。

比如:/['"][^'"]['"]/匹配的是用引号引起来的字符串,但是左右的单双引号可能不对称。如果使用子模式就可以解决这个问题:/(['"])[^'"]*\1/,其中\1表示对第一个子模式匹配结果的引用。

注意,对子模式的引用,不能放到字符组里去,比如不能这样:/(['"])[^\1]*\1/。

这种子模式及其引用,使用JavaScript有了强大的能力对字符串进行查找和替换。

另外如果不想对子模式产生一个引用的索引,可以把圆括号变成(?:...),即在左括号后面加上?:,如/([jJ]ava(?:[sS]cript)?)\sis\s(fun\w*)/中,\2表示的是(fun\w*)匹配的结果

总结一下:

|               --可选项,表示匹配左边或者右边

(...)           --组合,把括号中的字符组合成一个单独的单元,可以和? * + | 等一起使用。组合匹配的结果可以被记住,并在之后进行引用。

(?:...)        --单纯的组合,不能记住匹配的结果用来对其进行引用。

\n              --第n个匹配子模式的引用,n是通过从左到右数左括号的个数确定的,不过(?:除外。

5、定义匹配的位置(锚点)

正则表达式中的很多元素都表示匹配一个实际的字符,有一些特别的元素表示匹配字符之间的位置。比如\b匹配的是单词之间的边界,或者单词与字符串首尾的边界。

像\b这种元素,没有确定在匹配结果中的任何字符,然而他们确定了一个匹配结果应该发生的合法位置,我们把这样的元素称为正则表达式的锚点(regular-expression anchors)。

正则表达式锚点总结

^              --字符串的起始位置,在多行查找中,匹配每一行的开始

$              --字符串的结束位置,在多行查找中,匹配每一行的结束

\b             --匹配单词的边界,即:\w与\W之间的位置,或者\w与字符串起始与结尾之间的位置。注意:要匹配一个退格符,使用[\b]

\B             --匹配非单词的边界,与\b正好相反。

(?=p)        --匹配一个位置,该位置上接下来的字符符合模式p,匹配结果中不包含p匹配的结果。如/Java(?=:)/匹配"Java: program language"中的Java,但是不匹配"Java program language"

(?!p)         --匹配一个位置,该位置上接下来的字符必须不符合模式p,匹配结果也不含p匹配的结果,与上面刚好相反。比如/Java(?!Script)([A-Z]\w*)/匹配Java开头,后面跟一个大写字母以及任意个其他单词,但是Java后面不能跟Script。所以这个正则表达式匹配:JavaScrpt,JavaBean,但是不匹配Javabean,JavaScript,JavaScripter

6、标识

正则表达式最后一个语法元素是标识,标识定义个更高级别的匹配规则,标识没有定义在双斜杠之间,而是定义在第二个斜杠后面。JavaScript支持3个标识:

i       --表示匹配是非大小写敏感的

g      --表示匹配是全局的,在查找字符串中,所有的匹配项都必须找到

m     --表示多行查找匹配模式,该模式下,^  $除了匹配整个查找字符串的首位外,还匹配每一行的首位位置。

三、与模式匹配相关的String方法

String对象支持4个方法使用正则表达式,search

1、search:使用一个正则表达式参数,返回第一个匹配的位置,没有匹配返回-1,如:"JavaScript".search(/script/i)返回4,如果参数不是正则表达式,使用RegExp构造函数把它转成正则表达式,search不支持全局标识g,如果包含的话,会被忽略掉。

2、replace:使用两个参数,第一个为正则表达式,第二个为替换的字符串。如果第一个参数是字符串,该函数不会像search一样把它先转成正则表达式,而是直接搜索该字符串。该函数支持全局标识g,如果使用g,则会把所有匹配的结果用第二个参数替换,否则只替换第一个匹配结果。如:text.replace(/javacript/ig,"JavaScript"),把text中的所有javascript替换成正确的大小写形式。

如果第二个参数中包含$加一个数字的话,则替换的字符串会用匹配的结果替换掉这两个字符,然后再作为新的替换字符串去替换原来的字符串。比如包英文的引号换成中文的引号:text.replace(/"([^"]*)"/g,'”$1“');

3、match:使用一个正则表达式参数,不是正则表达式,先转为正则表达式。返回一个字符串数组。如果正则表达式是全局模式,返回的是所以匹配结果的数组。如果正则表达式是非全局模式的,返回的也是一个数组,数组第一个元素为匹配结果,其他元素是正则表达式中子模式的匹配结果。

4、split:使用一个正则表达式参数,匹配结果作为分隔符,把字符串分隔成字符串数组,如果参数是字符串而不是正则表达式,则直接用字符串作为分隔符。如:"123,456,234".split(",")返回["123","456","234"]

"1,   3, 4  ,  5".split(/\s*,\s*/)返回["1","3","4","5"]

四、RegExp对象

RegExp提供了一个构造函数,3个方法,以及5个属性来定义和使用正则表达式对象。

1、构造函数:构造函数使用1个或者2个字符串参数,第一个参数定义表达式实体,第二个参数定义表达式标识,即i,g,m及其组合。如:var zipcode=new RegExp("\\d{5}","g");

2、5个属性:

source           --只读属性,包含正则表达式文本的字符串

global            --只读属性,是否全局模式,true/false

ignoreCase     --只读属性,是否忽略大小写模式,true/false

multiline         --只读属性,是否多行模式,true/false

lastIndex       --读写属性,整型,对于g模式下,存储下一次搜索的起始位置,方法test()和exec()会使用到。

3、2个方法

exec(),一个字符串参数,如果没有匹配,返回null,如果有匹配,返回匹配的字符串数组结果,类似String方法match的数组结果,数组第一个元素表示匹配结果,接下来的元素表示每一个子模式的匹配结果。在返回的结果数组对象上,新增了2个属性:index和input,index表示匹配在字符串中发生的位置,input表示查找字符串。如果正则表达式是全局模式,那么执行后会修改正则表达式对象上的lastIndex属性,表示下次搜索的起始位置。非全局模型下,lastIndex始终为0.

如: var pattern=/Java/g;

var text="JavaScript is more fun than Java!";

var result;

while((result=pattern.exec(text))!=null){

alert(result[0]  + result.index + pattern.lastIndex);

}

test(),一个字符串参数,如果匹配成功,返回true,否则,返回false,该方法和exec()类似,只是返回值不同。全局模式下,执行后也会修改正则表达式的lastIndex属性,可以在一个字符串上多次调用。

 

 

posted on 2013-11-12 18:12  leungrs  阅读(1032)  评论(0编辑  收藏  举报