首页 » 编写高质量代码:改善JavaScript程序的188个建议 » 编写高质量代码:改善JavaScript程序的188个建议全文在线阅读

《编写高质量代码:改善JavaScript程序的188个建议》建议40:正确使用正则表达式分组

关灯直达底部

所谓分组,就是通过使用小括号语法分隔符来包含一系列字符、字符类,或者重复类量词,以实现处理各种特殊的字符序列。例如,针对下面的字符串,希望分拣出每个标签:


var s=/"<html><head><title></title></head><body></body></html>/";


如果使用贪婪模式进行匹配,虽然可以抓取所有标签,但是并没有分开每个标签:


var r=/<.*>/

var a=s.match(r);/*单元素数组[/"<html><head><title></title></head><body></body></html>/"]*/


如果使用惰性模式进行匹配,每次仅能够抓取一个标签:


var r=/<.*?>/

var a=s.match(r);//[/"<html>/"]


但是,如果利用分组来进行匹配,则可以获取每个标签的名称:


var r=/(<.*?>)/g;//分组模式

var a=s.match(r);//全局匹配标签,并存储到数组a中

for(var i=0;i<a.length;i++){//遍历数组a,获取每个标签的名称

alert(a[i]);

}


在上面的示例中,通过小括号逻辑分隔符,实现分别存储每个被匹配的标签,最后通过这个数组来获取每个标签的名称。注意,对正则表达式来说,小括号表示一个独立的逻辑域,其匹配的内容将被独立存储,这样就可以以数组形式读取每个子表达式所匹配的信息。

再看一个示例,假设准备匹配下面这个长字符串:


var s=/"abcdef-abcdef-abcdef-abcdef-abcdef/";


如果不使用分组,那么可能实现的正则表达式如下:


var r=/abcdef-abcdef-abcdef-abcdef-abcdef/;

var a=s.match(r);//单元素数组[/"abcdef-abcdef-abcdef-abcdef-abcdef/"]


尽管这是可以的,还是有点低效。如果不知道该字符串中到底出现几次重复,这时候可以使用分组来重写这个表达式:


var r=/(abcdef-?)*/;//分组模式进行匹配

var a=s.match(r);//[/"abcdef-abcdef-abcdef-abcdef-abcdef/",/"abcdef/"]


在小括号内的匹配模式表示正则表达式的子表达式,而跟随在小括号后的重复类数量词将会作用于子表达式,而不是字符“)”。因此,在上面的示例中通过小括号把每个标签内容作为匹配对象,然后通过重复类星号不但能进行迭代匹配,最终能够快速实现匹配的目的。

当然,并不限制在分组后使用星号,还可以使用任意重复类数量词:


var r=/(abcdef-?){5}/;//连续匹配5次子表达式

var r=/(abcdef-?){1,5}/;//最多匹配5次子表达式

var r=/(abcdef-?){0,}/;//匹配任意次子表达式

var r=/(abcdef-?)?/;//最多匹配一次子表达式

var r=/(abcdef-?)+/;//最小匹配一次子表达式


如果混合使用字符、字符类和量词,甚至可以实现一些相当复杂的分组,例如:


var s=/"<html><html><html><html></html></html></html></html>/";

var r=/<([/s]*?)html(s)*?>/g;

var a=s.match(r);//[/"<html>/",/"<html>/",/"<html>/",/"<html>/",/"</html

>/",/"</html>/",/"</html>/",/"</html>/"]


在上面的正则表达式中,使用了两个分组:第一个分组中包含了一个字符范围类,其中可以任意匹配空格或斜杠,文本范围类附加了一个量词“*”,表示空格或斜杠可以出现任意次数,为了避免正则表达式的贪婪性,在重复类量词后面附加了问号(?),使其以惰性模式进行匹配;第二个分组是一个简单的任意空格匹配。当然可以不分组,但是第一个分组是必需的,因为数量词作用于子表达式,而不是某个特定的字符。通过上面正则表达式,可以匹配任意形式的<html>标签,这样就不用担心空格或斜杠对匹配语义的影响。在正则表达式中,分组具有极高的应用价值,下面进行简要说明。

❑把单独的项目进行分组,以便合成子表达式,这样就可以像处理一个独立的字符那样,使用|、+、*或?等元字符来处理它们。例如:


var s=/"javascript is not java/";

var r=/java(script)?/g;

var a=s.match(r);//[/"javascript/",/"java/"]


上面的正则表达式可以匹配字符串“javascript”,也可以匹配字符串“java”,因为在匹配模式中通过分组可以使用量词“?”来修饰该子表达式,这样匹配字符串时,其后既可以有“script”,也可以没有。

❑在正则表达式中,通过分组可以在一个完整的模式中定义子模式。当一个正则表达式成功地与目标字符串相匹配时,也可以从目标字符串中抽出与小括号中的子模式相匹配的部分。例如:


var s=/"ab=21,bc=45,cd=43/";

var r=/(w+)=(d*)/;

var a=s.match(r);//[/"ab=21/",/"ab/",/"21/"]


在上面的示例中,不仅要匹配出每个变量声明,还想知道每个变量的名称及其值。这时如果使用小括号进行分组,把需要独立获取的信息作为子表达式,就不仅可以抽出声明,还可以提取更多的有用的信息。

❑在同一个正则表达式的后部可以引用前面的子表达式。这是通过在字符“”后加一位或多位数字实现的。数字指定了带括号的子表达式在正则表达式中的位置,如“1”引用的是第一个带括号的子表达式,“2”引用的是第二个带小括号的子表达式。例如:


var s=/"<h1>title<h1><p>text<p>/";

var r=/(</?w+>).*1/g;

var a=s.match(r);//[/"<h1>title<h1>/",/"<p>text<p>/"]


在上面的示例中,通过引用前面子表达式匹配的文本来实现成组匹配字符串。

由于子表达式可以嵌套在其他子表达式中,因此它的位置编号是根据左括号的顺序来定的。例如,在下面的正则表达式中,嵌套的子表达式(</?w+>)被指定为“2”。


var s=/"<h1>title<h1><p>text<p>/";

var r=/((</?w+>).*2)/g;

var a=s.match(r);//[/"<h1>title<h1>/",/"<p>text<p>/"]


注意,对正则表达式中前面子表达式的引用,指的并不是那个子表达式的模式,而是与模式相匹配的文本。例如,下面这个字符串就无法实现匹配。


var s=/"<h1>title</h1><p>text</p>/";

var r=/((</?w+>).*2)/g;

var a=s.match(r);//null


虽然子表达式(</?w+>)可以匹配“<h1>”,也可以匹配“</h1>”,但是对于“2”来说,它引用的是前面子表达式匹配的文本,而不是它的匹配模式。如果要引用前面子表达式的匹配模式,则必须使用下面正则表达式。


var r=/((</?w+>).*((</?w+>))/g;

var a=s.match(r);//[/"<h1>title</h1>/",/"<p>text</p>/"]