复杂模式不仅仅由字符类和量词组成,也可以由分组、反向引用、前瞻和其他一些强大的正则表达式功能组成。下面将介绍这些概念。
1, 分组:在之一和之二里的正则表达式都是处理一个字符接一个字符正如所预期的,特定的字符序列(不仅包含单独的字符)可以重复它们自身。要处理字符序列,正则表达式支持分组功能。
分组是通过用一系列括号包围一系列字符、字符类以及量词来使用的。例如,要匹配字符串"dogdog"。使用前面的方法,可能的表达式应该类似:
var regPattern = /dogdog/;
尽管这是可以的,但是不简洁。如果不知道dog在字符串中到底出现几次时怎么办?这里,我们可以使用分组来重写此表达式:
var regPattern = /(dog){2}/;
表达式中的括号的意思是字符序列"dog"将在一行上连续出现两次。但是并不限制在分组后使用花括号,可以使用任意量词:
var reg1 = /(dog)?/; // match 0 or 1 occurrences of "dog"
var reg2 = /(dog)*/; // match 0 or more occurrencess of "dog"
var reg3 = /(dog)+/; // match 1 or more occurrencess of "dog"
通过混合使用字符、字符类和量词,可以实现一些相当复杂的分组:
var reg = /([bd]ad?)*/; // match zero or more occurrencess of "ba","bad", "da", "dad"
同时也可以将分组放在分组中间:
var reg = /(mom( and dad)?)/; // match "mom" or "mom and dad"
这个表达式要求"mom"是必需的,但是字符串" and dad"可以出现零次或一次。
利用这个特性,我们可以为JavaScript的string类加上trim功能:
String.prototype.trim = function()
{
var regExtraSpace = /^/s+(.*?)/s+$/;
return this.replace(regExtraSpace, "$1");
}
这个正则表达式将查找字符串开头的零个或多个空白,跟着是任意数目的字符(在分组中捕获的字符),最后在字符串结尾处又是零个或多个空白。通过配合使用string对象的replace()方法以及反向引用,就实现了该trim方法
2, 反向引用:每个分组都被存放在一个特殊的地方以备将来使用。这些存储在分组中的特殊值,我们称之为反向引用。反向引用是按照从左到右遇到的左括号字符的顺序进行创建和编号的。例如:表达式(A?(B?(C?)))将产生编号1~3的三个反向引用:
(1) (A?(B?(C?))); (2) (B?(C?)); (3) (C?)。
反向引用有几种不同的使用方法:
a, 首先,使用正则表达式对象的test(), match()或search()方法后,反向引用的值可以从RegExp构造函数中获得, 例如:
var sToMatch = "#123456789";
var regNumber = /#(/d+)/;
regNumber.test(sToMatch);
alert(RegExp.$1) // output "123456789"
这个例子尝试匹配后面跟着几个或多个数字的磅符合。并对数字进行分组以存储他们。在调用test方法后,所有的反向引用都被保存在RegExp构造函数中,从RegExp.$1(它保存乐第一个反向引用)开始,如果还有第二个反向引用,就是RegExp.$2。因为该组匹配了"123456789",所以RegExp.$1中就存储了这个字符串。
b, 然后,海可以直接在定义分组的表达式中包含反向引用。这可以通过使用特殊转义序列如/1,/2等实现。如:
var sToMatch = "dogdog";
var regDogDog = /(dog)/1/;
alert(regDogDog.test(sToMatch)); // output "true"
正则表达式regDogDog首先创建单词dog的组,然后又被特殊转义序列/1引用,使得这个正则表达式等同于/dogdog/。
c, 第三,反向引用可以用在string对象的replace()方法中,这通过使用特殊字符序列$1, $2等来实现。描述这种功能的最佳例子是调换字符串中的两个单词的顺序。如:
var sToMatch = "1234 5678";
var regMatch = /(/d{4}))(/d{4})/;
var sNew = sToMatch(.replace(regMatch, "$2 $1");
alert(sNew); // output "5678 1234"
在这个例子中,正则表达式有两个分组,每个分组有四个数字。在replace()方法的第二个参数中,$2等同于"5678", $1等同于"1234",对应于它们在表达式中出现的顺序。
3, 候选:候选操作符和ECMAScript的二进制异或一样,是一个管道符(|),它放在两个单独的模式之间。例如,如果要对同一个表达式同时匹配"red"和"black"时要怎么做呢?它们完全没有相同的字符。这时,用候选就可以实现。
var sToMatch1 = "red";
var sToMatch2 = "black";
var regRedBlack = /(red|black)/;
alert(regRedBlack.test(sToMatch1)); // outputs "true";
alert(regRedBlack.test(sToMatch2)); // outputs "true";
在这里,regRedBlack匹配"red"或"black",同时测试每个字符串都返回"true"。因为两个备选项存放在一个分组种的,不管哪个被匹配乐,都会存在RegExp.$1中以备将来使用(同时也可以在表达式中使用/1)。在第一个测试中,RegExp.$1等于"red",在第二个中,它等于"black"。也可以指定任何数目候选项,只要你能想到的,只要在之间加入候选操作符即可。
OR模式在实践中一种通常的用法时从用户输入中删除不合适的单词,这对于在线论坛来说是非常重要的。通过针对这些敏感单词使用OR模式和replace()方法,则可以很方便地在帖子发布之前去掉敏感内容:
var regBadWords = /badWord|anotherBadWord/gi;
var sUserInput = "this is a string using badword1 and badword2";
var sFinalText = sUserInput.replace(regBadWords, "****");
alert(sFinalText); // outputs "this is a string using ****1 and ****2"
这个例子中指定"badwords“是不合适的。表达式使用OR操作符来指定这个单词。当使用replace()方法后,所有非法单词都被替换成四个星号。
也可以用星号替换敏感词中的每一个字母,也就是说最后出现的文本中星号的数量和敏感词中的字符数量是一样的。使用函数来作为replace()的第二个参数,就可以达到这个目的:
var regBadWords = /badWord|anotherBadWord/gi;
var sUserInput = "this is a string using badword and anotherbadword";
var sFinalText = sUserInput.replace(regBadWords, function(sMatch) {
return sMatch.replace(/./g, "*");
}
alert(sFinalText); // outputs "this is a string using ******* and **************"
在这段代码中,作为第二个参数传给replace()的函数其实还使用乐另外一个正则表达式。当执行函数之后,sMatch包含乐敏感词汇中的一个。最方便快捷的将每个字符替换成星号的方法是对sMatch使用replace()方法,指定一个匹配任意字符的模式并将其替换成一个星号。
4, 非捕获性分组:创建反向引用的分组,我们称之位捕获性分组。同时还有一种非捕获性分组,它不会创建反向引用。在较长的正则表达式中,存储反向引用会降低匹配速度。通过使用非捕获性分组,仍然可以拥有与匹配字符串序列同样的能力,而无需存储结果的开销。
如果要创建一个非捕获性分组,只要在左括号的后面加上一个问号和一个紧跟的冒号:
var sToMatch = "#123456789";
var regNumber = /#(?:/d+)/;
regNumber.test(sToMatch);
alert(RegExp.$1); // output ""
这个例子的最后一行代码输出一个空字串,因为该分组是非捕获性的。正因如此,replace()方法就不能通过RegExp.$1变量来使用任何反向引用,或在正则表达式中使用它。
正则表达式又一个十分常用的方式是去掉文本中所有的HTML标签,尤其是在论坛和BBS上,这可以防止游客在他们的发帖中插入恶意或无意的HTML。删除HTML标记的正则表达式很简单,只要用一个简单的表达式:
var regTag = /<(?:.|/s)*?>/g;
这个表达式匹配一个小于号(<)后面跟着任何文本(这是在一个非捕获性分组内指定的),然后跟着一个大于号(>),这有效地匹配乐所有的HTML标签。这里使用非捕获性分组是因为在小于号和大于号之间出现的内容并不重要(这些都是要被删除的)。你可以使用这个模式给string对象创建自己的stripeHTML()方法:
string.prototype.stripeHTML = function() {
var regTag = /<(?:.|/s)*?>/g;
return this.replace(regTag, "");
};
使用这个方法也很简单:
var sTest = "<b>this would be bold</b>";
alert(sTest.stripeHTML()); // output "this would be bold"
5, 前瞻: 有时,可能希望,当某个特定的字符分组出现在另一个字符串之前时,才去捕获它。如果使用前瞻就可以让这个过程变得十分简单。
前瞻告诉正则表达式运算器向前看一些字符而不移动其位置。同样存在正向前瞻和负向前瞻。正向前瞻检查的是接下来出现的是不是某个特定字符集。而负向前瞻则是检查接下来的不应该出现的特定字符集。
创建正向前瞻要将模式放在(?=和)之间。注意这不是分组,虽然它也用到括号。事实上,分组不会考虑前瞻的存在。考虑下列代码:
var sToMatch1 = "bedroom";
var sToMatch2 = "bedding";
var regBed = /(bed(?=room))/;
alert(regBed.test(sToMatch1)); // output "true";
alert(RegExp.$1); // output "bed";
alert(regBed.test(sToMatch2)); // output "false"
在这个例子中,regBed只匹配后面跟着"room"的"bed"。因此,它能匹配sToMatch1而不能匹配sToMatch2。在用表达式测试sToMatch1后,这段代码输出RegExp.$1的内容是"bed",而不是"bedroom"。模式的"room"的部分是包含在前瞻中的,所以没有作为分组的一部分返回。
对于负向前瞻,要创建它,则要将模式放在(?!和)之间。前面的例子也可以改成用负向前瞻匹配"bedding"而不是"bedroom"。
var sToMatch1 = "bedroom";
var sToMatch2 = "bedding";
var regBed = /(bed(?!room))/;
alert(regBed.test(sToMatch1)); // output "false";
alert(regBed.test(sToMatch2)); // output "true";
alert(RegExp.$1) // output "bed"
这里,表达式变成只匹配后面不跟"room"的"bed",所以模式匹配"bedding"而不是"bedroom"。在测试sToMatch2后,RegExp.$1还是包含"bed",而不是"bedding"。
4, 边界: 边界用于正则表达式中表示模式的位置。下表列出乐几种可能的边界:
边界 描述
^ 行开头
$ 行结尾
/b 单词的边界
/B 非单词的边界
假设想查找一个个单词,但要它只出现在行尾,则可以使用符号($)来表示:
var sToMatch = "Important word is the last one."
var regLastWord = /(/w+)/.$/;
regLastWord.test(sToMatch);
alert(regLastWord.$1); // output "one"
例子中的正则表达式查找的是,在一行结束之前出现的跟着句号的一个或多个单词字符。当用这个表达式测试时,结果是"one"。还可以很容易地获取一行的第一个单词,如:
var sToMatch = "Important word is the last one.";
var regFirstWord = /^(/w+)/;
regFirstWord.test(sToMatch);
alert(regFirstWord.$1); // output "Important"
在此例子中,表达式查找行起始位置的一个或多个单词字符。如果遇到非单词字符,匹配停止,输出"Important"。这个例子也可以用单词边界实现:
var sToMatch = "Important word is the last one.";
var regFirstWord = /^(.+?)/b/;
regFirstWord.test(sToMatch);
alert(regFirstWord.$1); // output "Important";
这里,表达式用惰性量词来制度在单词边界之前可以出现的任意字符,且可出现一次或多次(如果使用贪婪量词,表达式就匹配整个字符串)。
使用单词边界可以方便地从字符串中抽取单词:
var sToMatch = "First second third fourth fifth sixth";
var regWord = //b(/S+)?/b/g;
var arrWords = sToMatch.match(regWord);
正则表达式regWord使用单词边界(/b)和非空白字符类(/S)从句子中抽取单词。注意行的开头和行的结束,通常由^和$表示的位置,对应地也认为是单词的边界。事实上,更加简单的方法是使用单词字符类(/w):
var sToMatch = "First second third fourth fifth sixth";
var regWord = /(/w+)/g;
var arrWords = sToMatch.match(regWord);
5, 多行模式:如果字符串包含多行的情况下,可以使用多行模式来获取值。要指定多行模式,只要在正则表达式后面添加一个m选项。这会让$边界匹配换行符(/n)以及字符串真正的结尾。
var sToMatch = "First second/nthird fourth/nfifth sixth";
var regWord = /(/w+)$/gm;
var arrWords = sToMatch.match(regWord);
多行模式同样也会改变^边界的行为,这时它会匹配换行符之后的位置。例如,前面的例子,要从字符串中获取"First", "third"和"fifth",则可以这么做:
var sToMatch = "First second/nthird fourth/nfifth sixth";
var regWord = /^(/w+)/gm;
若不指定多行模式,将只返回"First".