热门:网页模板.net视频教程JQueryMVCjsonExtJs源码示例三级联动JQuery菜单
您现在的位置:.Net中文社区>> AJAX编程>>正文内容

ExtJS 2.2模板之一regexp

发布时间:2009年02月02日点击数:
Ext JS模块模型

模块化是组件的实质。ExtJS通过正则表达式实现的模板是组件模块化的根基。本章将详细地分析正则表达式的构成及原理,为模板的实现打下强实的基础。对于模板的实现,ExtJS实现了基本模板和高级模板分别用来进行不同程度上的模块化处理。

5.1概述

模块化是把分散的各部分内容有机地组合起来,它是组件根基。模块化的目的是为了重用。如对于<div style=”….”>张三</div><div style=”….”>1983-09-24</div>的html片断,把它组成了一个整体,那如何去重用呢?

对于模块的重用,在目常生活中是做成模板,之后每个产品都按该模板进行制造生产,这样每个产品都省去了其共同部分的重复设计。而现在对于模块化的html片断来讲,也可以把其不变内容抽离起来,形成模板,变化的内容只要往该模板中对应位置插入就形成需要的内容。

上面html片断是代表个人信息的内容,在网页中通过指定样式显示出来信息内容的主体是张三,而应用中,我们希望其主体内容可以动态变化,比如变成李四,然样式部分不变,变化的仅仅是姓名和出生年月这两部分内部。

那接下的问题就是如何知道模板什么位置的内容是可以动态变化的呢?这就要求在其变化内容的位置做上标识,碰到该标识就采用动态变化的内容替换掉。该标识一般来讲都是采用特殊符号,如${},我们把这个特殊符号称为插值。

可以看出插值是通过一些特定符号去代表一部分内容,其和JavaScript中正则表达式相似,它也是采用一些符号去代表一批字符内容。不过它主要作用是用来匹配或查找字符串的。那么我们的模块化的内容能不能转化为字符串呢?如果能的话,就可以利用正则表达式来实现。现实中我们所要求的模块化内容一般都hmtl文档片断,也就是说它可以是字符串形式。

服务器页面技术如JSP都是采用类似的模块及模板思想。它们都是通过服务器标签去代表代表一部分html片断内容。那么我们的组件与服务器标签是相似的,它是在客户端进行模块化内容的处理。每个组件最终结果都是要显示出来,这就形成DOM元素用来显示,而DOM元素又如何采用html标签的字符串内容来进行转换(通过innerHTML)。把显示效果中一些不变的内容形成模板,把动态变化内容通过插值的形式动态插入,最终构建组件所要完成的显示功能。可以看出模板是组件化的核心思想。

无论如何,模板都是针对于字符串的操作,也就是采用一部分内容去替换另外一部分标识符号形成新的内容,这样的话就可以采用String中的replace方法来进行动态数据和插值部分替换。ExtJS就为String扩展了format函数进行简单的模板处理:
format : function(format){
  var args = Array.prototype.slice.call(arguments, 1);
  return format.replace(/\{(\d+)\}/g,  
function(m,i){return args[i]; });
}

在这个模板的实现中,它不是采用${}做为标识,而是采用{}做为标识。在{}中还有一些数字用来标识它的位置(与format参数是对应的)。这样其标识也是动态解析,它要解析字符串中的{1..n}的内部数字及其本身位置采用其内部数字指定的参数位置的值来取代。这个动态解析就用到了replace函数的一个高级特征。现在我们就采用这个函数把前面提到html片断构建成模板:
var template='<div style=\'color:red;\'>{0}</div>'+
'<div style=\'color:red;\'>{1}</div>';
var result=String.format(template, '张三','1983-09-24');

这样template就是模板,而result就是生成的模板内容。对于简单的模板应用来讲,format方法是非常有用的,但是对于复杂的情况就无能为力,比如张三要变成张先生这样称呼怎么办?生日也要换一种格式,如采用1983年9月24日,又该怎么办?当然其前提是数据不同。这就需要模板有动态处理能力。

为了解决类似问题,ExtJS提供了功能强大的Template技术,它包含基本的插值处理的Template类和实现常用指令功能的Xtemplate类。在Template中不仅支持通用的格式转换,如日期格式。还可以支持私有格式转换,如称呼上的转换,它依赖于信息主体的性别,和业务相关的格式转换。而在XTemplate,它支持for循环,if判断等指令能变成更强大的功能。

在讲这部分内容之前,我们得详细分析正则表达式,模板的实现依赖于正则表达式。

5.2正则表达式

正则表达式(Regular Expression)是开发中经常使用的技术。它是用来判断字符串是否匹配给定的表达式所要表示的特征。主要用来进行验证判断,字符查找,定位等方面的操作。

绝大多数读者应该已经了解过或使用过正则表达式,但是肯定有相当一部分读者是对正则表达式语法一知半解。可能在写验证时都要时常baidu一下或查阅一下身边资料。接下来几节我们就全面去了解其语法,让读者了记于心。正则表达式有很多引擎,浏览器采用是传统NFA引擎,下面先分析其引擎再来进行正则表达式语法的介绍。

5.2.1正则表达式引擎

了解正则表达式首先要了解其引擎。因为不同的引擎有着个自的特征,而目前的主流正则引擎又分为3类:传统NFA、POSIX NFA、DFA。

传统 NFA 引擎以指定顺序测试正则表达式的所有可能的扩展并接受第一个匹配项。如正则表达式/pr?k/和给定的’ oppropkprkpk’字符串,我们通过图表来演示它匹配的过程:

6-1传统NFA运行原理分析

次数

说明

第一次

首先根据表达中首字符p从给定字符串找到首个匹配p的字符,其位置为2,接着判断r?是否匹配接下来的p字符,满足且不占位,继续判断k否匹配该p字符,不满足就标识第一个p字符的位置,如op|propkprkpk

第二次

从给定字符串中标识的位置之后重新查找到首个匹配p的字符,在其位置为3,接下来判断r?是否满足接下来的r,满足且占位,然后用k来匹配给定字符串中接下来o字符,不满足,标识第二个p字符的位置,如opp|ropkprkpk

第三次

从给定字符串中标识的位置之后重新查找到首个匹配p的字符,其位置为6,接着判断r?是否匹配接下来的k字符,满足且不占位,继续判断k否匹配该k字符,满足,匹配完成。返回匹配结果pk字符串。



在表6.1中,可以看出它一共要进行三次匹配,这里是采用正则表达式去匹配给定字符串。最初正则表达式只用来判断真假关系,这里的判断肯定为真,但是后来人们想要取其匹配值,这里返回的结果是pk。并不是最优的匹配结果,它在匹配第一个满足结果时就返回不会继续查找后面可能会出现的最佳匹配结果(这里是prk),为了解决这个问题,出现了POSIX NFA引擎。

POSIX NFA引擎与传统NFA 擎不同就是它返回匹配结果是所有匹配子字符串集合中最长的字符串,即最优匹配。在表6.1中,它先把找到pk匹配字符串保存在集合中,之后进行第四步,找到prk保存在集合中,直到查寻到给定字符串的结束位置为止。这样集合中就有了pk,prk,pk三个匹配字符品再进行比较,找到第一个最长的字符串返回(也有可能最后一个最长的字符串,看其实现)。

NFA查找是有一些缺点的,上面例子我们是把r?做为一个整体来分析,但是在解析过程,它是经过两个过程的,第一个过程是判断r是否匹配,如果匹配接着判断接下的k是否匹配,如果r不匹配的话,就采用r?的第二种方式即没有r的情况下进行匹配,那就是直接采用k进行匹配了。这种就是表6.1中的占位与不占位的区别。这种先判断其表达式中符号代表最长的内容是否匹配,不匹配再一步步缩短其匹配内容进行匹配,这种模式叫贪心的匹配模式,这种不满足每次都进一步综短其匹配内容进行匹配的步骤叫作回溯。

?只匹配0或1个字符,那么对于+呢?如果正则表达式写得不好的话,就会出现很多次回溯,极为影响效率。我们来看一下正则表达式/X(.+)X/和'aX&Xaaaaaaaaaaa'字符串。它的(.+)首先是匹配&Xaaaaaaaaaaa,不满足,再匹配&Xaaaaaaaaaa,直到减少到&才满足。这个(.+)后面的匹配是比较简单的,只有一个X进行匹配,如果其后面比较复杂,那回溯的次数就可能会成几何级地增长。如/X(.+)+X/表达式。它先根据(.+)找到最长的匹配&Xaaaaaaaaaaa,这个时候其后面肯定不满足,接下来其会减少长度,如减少到&X这个时候,括号外面的+就&X为单位去从&Xaaaaaaaaaaa判断匹配开始一直缩短到&X时,表达式不满足。继续把(.+)的判断缩短为&,对于括号外面的+,现在以&为单位进行回溯判断,最终满足。可以出=越多其就性能就越差。

DFA 引擎就是为了解决这样的问题,它不是像NFA一样用正则表达式去匹配给定字符串,而是采用给定字符串去匹配正则表达式。对于表6.1中例子,我们采用DFA来分析:/pr?k/和给定的’ oppropkprkpk’

表6-2 DFA运行原理分析

步骤

位置

说明

1步:o

1

判断其是否和正则表达式中首个p字符匹配,否,进行第2步。

2步:p

2

判断其是否和正则表达式中首个p字符匹配,是,接下判断其后p字符是否和正则表达式p字符之后的r?相配,是且不占位,其继续和正则表达式k字符进行匹配,否,进行第3步。

3步:p

3

判断其是否和正则表达式中首个p字符匹配,是,接下判断其后r字符是否和正则表达式p字符之后的r?相配,是且占位,接下判断其后o符是否和正则表达式k字符进行匹配,否,进行第4步。

4步:r

4

判断其是否和正则表达式中首个p字符匹配,否,进行转第5步。

5步:o

5

判断其是否和正则表达式中首个p字符匹配,否,进行转第6步。

6步:p

6

判断其是否和正则表达式中首个p字符匹配,是,接下判断其后r字符是否和正则表达式p字符之后r?相配,是且不占位, 其继续和正则表达式k字符进行匹配,是则保存匹配pk,进行第7步。

7步:k

7

判断其是否和正则表达式中首个p字符匹配,否,进行第8步。

8步:p

8

P和表达式中p相配,接着判断其后的r是否和r?相配,满足,接着采用kr?之后的k进行判断。满足。把prk保存起来。进行第9步。

9步:r

9

判断其是否和正则表达式中首个p字符匹配,否,进行第10步。

10步:k

10

判断其是否和正则表达式中首个p字符匹配,否,进行第11步。

11步:p

11

判断其是否和正则表达式中首个p字符匹配,是,接下判断其后r字符是否和正则表达式p字符之后r?相配,是且不占位, 继续和正则表达式k字符进行匹配,是,保存匹配pk,进行第12步。

12步:k

12

判断其是否和正则表达式中首个p字符匹配,否,进行第13步。

13

结束

从三个结果中找到第一个最长的字符串prk返回。


由表6.2可以看出,两种引擎的工作方式完全不同,NFA以表达式为主导,DFA以文本为主导。对于/X(.+)+X/和'aXXaaaaaaaaaaa'来说会快很多。但是很多的语言的实现都是采用传统NFA,浏览器也是。这是因为NFA构造正则表达式会多次回溯,那么就可以捕获子表达式匹配和匹配的反向引用。

子表达式匹配是采用括号括起来部分相匹配的内容,它保存在特定地方,如$1~$99。反向引用是对于保存子表达式匹配的字符串可以通过特殊字符在正则表达式中代替其相同的内容。如/(.+)is \1/,这里的\1就是反向引用,它用来代表与第一个括号中的内容相同的内容,这里就可以匹配dog is dog之类的字符串。

通过这三种正则表达式的引擎的分析,我们可以看出正则表达式书书的好坏会严重地影响地到其JS代码的执行效率。在使用浏览器中,我们不能选择不同的表达式引擎,只能在传统NFA引擎上进行编写高性能的表达式,这就要求我们对于正则表达式的语法进行深入地了解和学习。

5.2.2字符匹配

在正则表达式中,所有字母字符和数字都是按照字面意思与自身相匹配的,对于其它字符,一般都代表特殊意义,需要采用\转义来进行让其返原其本身的字符,但是有一些采用\转义的字符其本身在字符串中就是代表一个字符:

表6-3 字符中转义字符

\b 后退一格(Backspace

\f 换页(Form Feed

\n 换行(New Line

\r 返回(Carriage Return

\t 制表(Tab

\\ 反斜线(Backslash

\' 单引号

\" 双引号

\uFFFF  unicode字符

\x23  ASCII码字符


表6.3中是字符串转义的字符。而如对于如\ /、\ .、\ *、\ ?、\ |、\ (、\ )、\ [、\ ]、\ { 、\ }等则是在正则表达式中进行返回转义来代表其字符本身意思,如直接的*在正则表表达中就代表匹配0个多个字符。

但是这些非字母或数字字盘在正则表达式的[]中,基本上都是不需要采用\来来返原其字符本身。因为方括号代表的是字符类匹配,它中间的所有字符都是标准字符,除一些特殊的符号需要反转义。

单字符匹配

把单个或多个字符放在方括号中是用来创建待匹配的列表。方括号表达式中所包含的字符只匹配该括号表达式在正则表达式中所处位置的一个单字符。如prk[123]可以匹配prk1、prk2、prk3三种,但是它只匹配字符串的一个字符是否满足于方括号中一个字符。

括号内普通字符代表其本身,大多数特殊字符在位于括号表达式中时都将失去其含义。这里有一些例外。

']' 字符。在方括号中首位置不必转义,其它位置则需要反转义。

'\' 字符。在方括号中仍然作为转义符。需要反转义。

'-' 字符。在方括号首尾位不必转义,其它位置则需要反转义。

'^' 字符。在方括号首位置必需转义,其它位置是不必。

其实上面四个字符不需要记忆,如']'在方括号中,我们是怎么知道其是结果方括号还是字符呢,不知道必须反转义。在首位置不必,那是方括号中至少要有一个字符。对'\'它来是字符串转义的字符,当然得要采用\\表示。

'-'连字符表示范围,对于如a-z中间一个字符,我们总不能每个都列出来吧。连字符前面的字符码值要小于后面的码值,如采用/^[\u4e00-\u9fa5]来标明其是汉字,另外方括号里还可以有多个连字符表示的范围,如[a-zA-Z],[a-zA-Z1-9]等。

上面在范围之外,如果要在范围之外呢,这时就得采用^在方括号开始处来标明,如[^a-zA-Z]用来匹配非字母的字符。所以'^' 字符只有在方括号开始处需要反转义。

由于某些字符类非常常用,正则表达式语法包含一些特殊字符和转义序列来表示:

表6-4 字符常用转义

字符

单词记忆

匹配

[...]

位于括号之内的任意字符

[^...]

不在括号之中的任意字符

.

除了换行符之外的任意字符,等价于[^\n]

\w

word

等价于[a-zA-Z0-9]

\W

word

任何非单字字符,等价于[^a-zA-Z0-9]

\s

space

任何空白符,等价于[\t\n\r\f\v]

\S

space

任何非空白符,等价于[^\t\n\r\f\v]

\d

digit

任何数字,等价于[0-9]

\D

digit

除了数字之外的任何字符,等价于[^0-9]

[\b]

backspace

一个退格直接量(特例)


这些特殊字符很好记忆,其都是常用单词的首字母的小写。大写形式表明与其相反。如\w表明单字符,而\W则是非单字符。

在Ext.escapeRe就用到这样的正则表达式/([.*+?^${}()|[\]\/\\])/g,它包括了很多特殊符合,这些符号在[]中都是字符本义。这里其实\/是不需要进行反义的。
escapeRe : function(s) { return s.replace(/([.*+?^${}()|[\]\/\\])/g, "\\$1");},

不同是它是对于正则表达式进行处理,我们按通常的格式写好正则表达式字符串,当我们通过new Regexp把这个字符串构建正则表达式时,它也会先对表达式中的字符串进行默认的转义操作,而正则表达式本来也是采用\或默认的字符进行转义。很有可能先转义了,就没有达到所要的效果。它的意思是在s字符串的下面任何一个字符:.、*、+、?、^、$、{、}、(、)、|、]、/、\前后加\字符形成新的字符串。对使用如何Regexp来构建时,它会在解析这字符串,把这个\转义掉。对于那些不是转义的词符,就直接去掉\。在这里我们要注意的是对于在正则表达式的方括号中的很多字符仅仅是代表其本身的字符,而不是转义之后代表的意义。

多字符匹配

使用方括号或其特殊转义符号都能人用是描述匹配单个字符,如我想匹配四个数字呢?那得采用 / \d \ d \ d \ d /四位数描述。为了解决这个问题,于是就出现了修饰符的概念,它用来限定其前一项的重复次数。

表6-5 重复次数修饰符

字符

含义

{n, m}

匹配前一项至少n,但是不能超过m

{n, }

匹配前一项n,或者多次

{n}

匹配前一项恰好n

?

匹配前一项0次或1,也就是说前一项是可选的. 等价于{0, 1}

+

匹配前一项1次或多次,等价于{1,}

*

匹配前一项0次或多次.等价于{0,}


表6.5中重复次数修饰符是用来指定其前一项重复的次数,它是采用大括号中含有指定前一项重复次数范围。对于常用的重复次数,它还定制?,*,+几种特殊字符来表示。

限定符的前一项是一个基本单元,如(),[],单个字符或转义字符。如

/\d{2, 4}/ //匹配2到4位的数字.

/\w{3} \d+/ //匹配三个字符和一个以上数字

/[^"] * / //匹配零个或多个非引号字符。

在使用重复次数修饰符来重复匹配不定次数的表达式时,它总是采用尽可能多的匹配。这就是所谓的贪心模式。比如,针对文本 "axxxaxxxa",(d)(\w+)(d)举例如下:

表6-6 贪心模式匹配

表达式

匹配结果

(a)(\w+)

"\w+" 将匹配第一个 "a" 之后的所有字符 "xxxaxxxa"

(a)(\w+)(a)

"\w+" 将匹配第一个 "a" 和最后一个 "a" 之间的所有字符 "xxxaxxx"



在表6.6中,我们可以看出其是找到最长的匹配,但是这里要和表达引擎中传统NFA和POSIX NFA区分开来,这里指的仅仅是限定符采用了贪心模式,而不是找到满足表达式之后的字符串还会继续查找。

那我们正在有一个问题了,如果我们找到第一个"a"和第二个"a"之间的匹配怎么办?这里就得采用非贪心模式。所谓的非贪心模式就是它是从最少的匹配开始,对于非贪心模式只在表6.5中限定符之后加上?就可以了。

对于"axxxaxxxa"采用非贪心模式的表达式如下:(a)(\w+?)(a)。这时(\w+?)第一次只匹配在第一个a后面的一个x,不整个表达式不匹配之后再去增加这个匹配的长度,直到三个x满足匹配为止。

采用非贪心模式和贪心模式的区别,有的时候是能提高整个表达式运行的性能。如同aXXaXaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa相对于/X(.+)+X/表达式,浏览器会警告其运行时间过长,而采用/X(.+?)+?X/方式则一下子就运行出来了,其效率不知高多少倍,在写正则表达式时,采用限定符时是要考虑到其性能的。

5.2.3.选择、分组及反向引用

正则表达式还提供选择、分组和引用三个来完成更复杂的匹配操作:

表6.7 选择、分组和引用

分类

表达式

作用

选择

|

左右两边表达式之间 "" 关系,匹配左边或者右边

分组

( )

做为一个单元整体来进行操作。

引用

\n

n为数字,它是用来引用表达式前面第n个子表达式内容。


对于上表中的三种,我们分别来做介绍。

1、选择

使用正则表达式时,我们经常要用来选择来完成任务,比如要么是ab,要么是cd。对于选择语法,它是采用通用的|符号,它的左右边是供选择的选择项,如/ab|cd/,我们不能理解为b与c之间的选择。它的意思是要么ab,要么cd。

那么如果我们就想b与c之间的选择呢?那就是采用()把它当作一个单元整体来看,如/a(b|c)d/。对于没有采用()整合成一个单元整体的表达式,其范围是从表达式的开始到结束。这是两者之间的选择,对于三者之上选择呢?如/ab|cd|ef/,它表示ab、cd、ef三者之间选一。其范围和二者选择的范围是一样的。

对于选择是一定理解其范围中,在方括号中,其是没有选择的,不可能有[a|b],方括号本身就是一个选择,在其内部字符中选择一个来满足。如[a|b],它表明是满足a,|,b三个字符之一就是匹配。

2、分组

分组是采用括号把正则表达式中部分内部组合整体,在操作时可以当作一个单元,如/a(bc){2}/。这里就是采用括号把bc组成一组,重复次数修饰符就可以对这个整体进行操作,不然就是对c进行修饰。

括号的第二个用途是捕获子表达式匹配。有的时候我们关注并不一定是匹配的整个表达式内容,而是这个内容的一部分。如我们想取到图灵出版社出版的书名,如/turing book ExtJS/这样的表达式是满足的,但是我们在这个满足的情况取到具体的书名ExtJS。这时就可以采用()来进行捕获子表达式匹配,如/turing book (ExtJS)/,这样书名就保存起来了。这个保存的在$1~$99的变量中。如果我们对整个表达式多个部分感兴趣的话,可以多个括号进行捕获,它还可以是嵌套的括号,其保存在$1~$99按照左括号出现的顺序来排列。如/a(b(cd)e)(fg)l/,那么$1的内容是bcde,$2的内容是cd,$3的内容是fg。

现在问题是分组不需要其把捕获子表达式匹配保存,因为这样的实现从性能内存等方面更为经济。这里我们采用(?:表示式)的形式来分组,达到非捕获。这对于采用重复次数修饰符或采用选择符进行分组时是很好的选择,如/ExtJS 2.1|ExtJS2.2/与/ExtJS (?:2.1|2.2)。

笔者在以前使用(?:表示式)的形式碰到这样的问题,就是要采用正则表达式统计并打印一个指定数字之的所有数字,如'43445333336323',我要找到3前面所有数字,并按顺序排列出来,那么对于连续的3前面3也要打印出来,想要的结果是4,5,3,3,3,3,6,2。这样可以采用一个简单的方法来实现:document.write('43445333336323'.match(/\d(?:3)/g);发现其结果与希望相差甚远,其结果是43,53,33,33,63,23。

这里有两个问题是搞清楚的,第一match采用g检索返回的结果和子表达式有任何关系,这个非子捕获不能用去其后面的3。第二个问题是大问题,它统计的次数少了2次,其连续的3它不会去计算的。

这时我们就要用到正则表达式中的正向预查,所谓的正向预查是要查询其是否满足指定的条件,但是满足预查的这些字符并不占位。它的格式为"(?=xxxxx)"。我们把'43445333336323'的实现改成正向取预查:document.write(s.match(/\d(?=3)/g));

这时就满足了条件,我们给出其在match中的标识位的变化。

6-8  正向相等预查

次数

说明

1

判断43是否满足表达式,满足,把满足的4保存,其下一次查询的开始位置为'4|3445333336323'中的|处。说明其预查的3要判断其满足,但是不占位

2

接下来34,44,45都不满足。53满足,保存5。位置为'43445|333336323'

3

现在5后面的33是满足的。那么保存3,其位置为'434453|33336323'

4

其它步骤相似。


从表6.8可以预查是要满足预查给定的条件,但是并不要去匹配这个条件,它不占位。这就是预的意思。如果是不满足预查表达式中的内容,可以采用正向非等预查:"(?!xxxxx)"。如表达式 "do(?!\w)" 在匹配字符串 "done, do, dog" 时,只能匹配 "do"。在本条举例中,"do" 后边使用 "(?!\w)" 和使用 "\b" 效果是一样的。

3、反向引用

在对于字符串的操作,经常会要求字符串中子串对称的情况,最常见是"和',它们一定要有一个对应的来结束。如在JS中,"和'都可以用,现在要求字符串要么是用"来引用或采用'来引用,不能交叉使用。这个时候使用上面语法是解决不了问题。

这就是我们要说的反向引用。反向引用,就是先用()把要引用的保存起来,在需要进行判断其相同内容的时候加入。允许我们在同一正则表达式的后面引用前面的子表达式.这是通过在字符串 \ 后加一位或多位数字来实现的.数字指的是代括号的子表达式在正则表达式中的位置.例如: \1 引用的是第一个代括号的子表达式. \3 引用的是第三个代括号的子表达式.注意,由于子表达式可以嵌套在其它子表达式中,所以它的位置是被计数的左括号的位置。

对正则表达式中前一子表达式的引用所指定的并不是那个子表达式的模式,而是与那个模式相匹配的文本.这样,引用就不只是帮助你输入正则表达式的重复部分的快捷方式了,它还实施了一条规约,那就是一个字符串各个分离的部分包含的是完全相同的字符.例如:下面的正则表达式匹配的就是位于单引号或双引号之内的所有字符.但是,它要求开始和结束的引号匹配(例如两个都是双引号或者都是单引号):/[' "] [^ ' "]*[' "]/。

如果要求开始和结束的引号匹配,我们可以使用如下的引用:/( [' "] ) [^ ' "] * \1/。\1匹配的是第一个代括号的子表达式所匹配的模式.在这个例子中,它实施了一种规约,那就是开始的引号必须和结束的引号相匹配.注意,如果反斜杠后跟随的数字比代括号的子表达式数多,那么它就会被解析为一个十进制的转义序列,而不是一个引用.你可以坚持使用完整的三个字符来表示转义序列,这们就可以避免混淆了.例如,使用 \044,而不是\44。

5.2.4位置及优先级

前面我们讲到正则表达式都是用来匹配字符串的字符。但在需求不仅仅仅是这样,比如我要想匹配一个字符串完完全全地等同于prk,怎么办,采用前面的方法是行不通。它会在字符串查找到满足的字符串。完完全全地等同就是要求正则表达式开始位置等于给定字符串的开始位置。其结束位置等于给定字符串的结束位置。

这就是我们要讲的位置匹配。它指定的是匹配所发生的合法位置,我们称这些元素为正则表达式的锚(anchor),因为它们将表达式定位在检索字符串中的一个特定位置。对于定位的锚有如下几种:

表6-9 锚定位

字符

说明

^

匹配的是字符的开头,在多行检索中,匹配的是一行的开头

$

匹配的是字符的结尾,在多行检索中,匹配的是一行的结尾

\b

匹配的是一个词语的边界。位于也就是指单词和空格间位置,不匹配空格。

\B

匹配的是非词语的边界的字符


对于前面讲到完完全全等同,就可以采用/^prk$/。锚是用来匹配的位置,在正则表达式,解析它们也位置的要求,这个位置就是解析的优先级。通过前面可以看到\的优先级最高。如下表:

表6-10 优先级

操作符

描述

\

转义符

() (?:) (?=)(?!)[]

圆括号和方括号

* +? {n}{n,} {n,m}

限定符

^$\b\B

位置和顺序

|

操作


在表6.10中,我们发现转义符优先级最高。接下来是构成单元整体的括号和方括号。括号是用来分组,子表达式捕获,反向引用,正反向预查等功能,而方括号是用来代表其是字符类内容选择的整体。在这个整体中字符基本上不被转义。

接下的优先级是重复次数修饰符,用来指定单个字符或括号和方括号组成的单元整体的重次出现的次数。位置符处于限定符之后。最低的是或操作。

5.2.5正则表达式的使用

在前面几小节,我们已经讲了正则表达式的语法,那么接下就应该是它的使用。构成正则表达式对象很简单,有二种方法,一种是采用/ pattern /[flag]直接量的形式,第二种是采用new RegExp("pattern"[,"flags"])的形式。对于采用第二种形式,因为它的pattern是字符串,那么为了避免冲突,所有\前面都要加上\保留其被解析消化掉。因为字符串内部对于\的处理是把它进行转义的特殊符号。

其参数pattern是表达式内容,其flags表达式的搜索模式,有两个可选参数,分别是i和g。g(global)代表全局搜索。采用了g就表明其不是传统NFA引擎找到就不找了。它要找遍整个字符串,把所有满足的保存起来。i(ignoreCase)是用来表明忽略大小写。它们两个可以综合起来用。

在讲分组时,我们讲到捕获子表达式中的字符串是保存在$1~$99中,那这些属性是保存在window对象属性下还是哪里?保存了在window下冲突的可能性很大,于是就采用了在RegExp本身对象来保存,也就是说一个正则表达式对象的所有函数都共用这么一些属性,它会随时改变的,在执行完一个正则表达式之后或当中应该立即使用它们来匹配或保存其它位置。

它的属性在不同的的函数执行时都可能会动态增加,具体见每个函数,但是一般来说都会有如下的属性:global、ignoreCase、multiline三种,它们用来保存正则表达式指定的flage,分别与g,i,m对应起来。

这里可以看出除属性 g 和 i 之外,正则表达式还可以通过m来设定其处理的字符串是多行文本的形式,因为多行文本要进行特殊的处理。在多行的模式下,锚字符 ^ 和 $ 匹配的不只是检索字符串的开头和结尾,还匹配检索字符串内部的一行的开头和结尾.例如: 模式 /prk$/ 匹配的是 "prk",但是并不匹配"prk\n pch"。如果我们设置了 m属性,那么后者也将被匹配。不过有的不支持直接通过flag来设定,我们可以通过RegExp.multiline = true;来设定。

定义了生成正则表达式对象,那么就得用它来进行一些操作,正则表达式的操作主要有判断,查询定位等,因为它是对字符串的操作,在字符串的函数中也集成了内部支持正则表达式,我们先通过列表来看一下这样函数。

表6.11与正则表达式相关的函数

函数

说明

RegExpObj.test(str)

用于检测一个字符串是否匹配指定正则表达式。匹配返回 true,否则返回 false

RegExpObj.exec(str)

用于检索字符串中的正则表达式的匹配。返回一个数组,存放匹配的结果。如果未找到匹配,则返回值为 null

stringObj.match(regexp)

判断字符串是否与参数中指定的正则表达式相匹配。返回存放匹配结果的数组。

stringObj.replace(regexp,str)

用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配子串。

stringObj.search(regexp)

用于检索字符串中指定子字符串,或检索与正则表达式相匹配子串。返回首个匹配子串起始位置,没有找到就返回 -1。因采用string构建,/之上要多加一个/号。

stringObj.split(sep, h)

通过指定分隔符把字符串分割成字符串数组。该分隔符可以通过正则表达式指定。


上表中我们给出正则表达式相关的函数,其中test、exec是正则表达式对象的函数,其它的都是这字符对象的函数。这些函数大部分很简单,不多简单,我们就复杂的exec、match、replace进行专门地介绍。

exec和match

RegExpObj.exec(str)与stringObj.match(regexp)是一对功能很相似的函数,它们分别是针对于RegExtObj和StringObj而进行的操作函数。它们都返回数组,功能都很强大,在没有指定g的全局搜索情况,它们的作用是一样。

如果 regexp 没有标志 g,找到了匹配的文本,则返回一个结果数组。否则返回 null。该数组的第 0 个元素存放的是匹配文本,而其余的元素存放的是与正则表达式的子表达式匹配的文本。返回的数组还含有两个对象属性。index 属性声明的是匹配文本的起始字符在被检索的字符串中的位置,input 属性则存放的是被检索的字符串string。这些属性原来都是在存在RegExp静态对象中属性中,为了避免冲突,在其处理过程中就另存起来,以返回数组对象的形式保存。

如果 regexp全局正则表达式时,exec() 的行为就稍微复杂一些。它会在 RegExpObject 的 lastIndex 属性指定的字符处开始检索字符串 string。当 exec() 找到了与表达式相匹配的文本时,在匹配后,它将把 RegExpObject 的 lastIndex 属性设置为匹配文本的最后一个字符的下一个位置。这就是说,您可以通过反复调用 exec() 方法来遍历字符串中的所有匹配文本。当 exec() 再也找不到匹配的文本时,它将返回 null,并把 lastIndex 属性重置为 0。

而 match() 方法执行全局检索时,查找到所有匹配子字符串。若没有找到返回 null。如果找到了一个或多个匹配子串,则返回一个数组。不过其返回的数组元素存放的是 stringObject 中所有的匹配子串,而且也没有 index 属性或 input 属性。

replace函数

stringObj.replace(egexp/substr, replacement [,flags])函数是用来查找并替换的函数。它将根据第一个参数给定的子字符串或给定正则表达式相匹配的子字符串在当前字符串中查找其相匹配的子字符串,然后用第二个参数中指定的字符吕来替换查找到子字符串。如果 正则表达式指定全局搜索标志g,那么它将查找所有满足于子符串并替换掉,否则只替换第一个匹配子字符串。

它的第二个参数可以是替换字符串,也可以是用来进行替换操作的函数。如果字符串,那么匹配的内容都将由它替换。如果是函数,每次匹配时都会调用执行该回调函数,采用它返回的字符串作为替换文本使用。

该函数是由replace内部调用,其参数也是内部传入。它的第一个参数是当前这次整个正则表达式相匹配的字符串。接下来的参数依次当前次捕获的0个或多个子表达式相匹配的字符串。接下来的参数是用来指定当前相配的字符串在整个字符串中的位置,最后一个参数是 整个字符串本身。

可以看出其是函数的话,相关的信息都通过参数的形式传入,如何不是函数,是字符串的话,想替换与匹配信息相关的内容怎么办,这时就可以在替换字符串采用$打头的标识符,用来标识其位置是那一些匹配信息。其标识如下:

6.12 replace中替换内容的标识

字符

替换文本

$1$2...$99

regexp 中的第 1 到第 99 个子表达式相匹配的文本。

$&

regexp 相匹配的子串。

$`

位于匹配子串左侧的文本。

$'

位于匹配子串右侧的文本。

$$

直接量符号。


这样我们就不要通过函数也能完成简单的动态替换的功能,下面我们给出一个例子来演示。
var mm="{first:u0012},{second:u0023},{third:u0024}";        ①

var  m1=mm.replace(/{(\w*):(\w*)}(?=(,?))/g,"当前匹配字符串是\"$&\",  ②

其中{$1:$2}之前内容为\"$`\",其后内容为\"$'\"。<br/>");

document.write(m1+"<br/>");    

var dd=function(match,sub1,sub2,start,whole){       ③

      var m="\""+match+"\"位于\""+whole+"\"中第"+start+"处。";

      m=m+"它包含\""+sub1+"\"和\""+sub2+"\"。<br/>";

      return m; }
;  

var m2=mm.replace(/{(\w*):(\w*)} (?=(?:,?))/g,dd);   ④

document.write(m2);

这一段代码采用含有所有$的应用的替换字符串和采用函数来替换。其中②处采用了正向预查用来去解显示中的,④采用了非捕获减少③函数的参数,这里不给出其效果,读者对着自行分析其显示出来的结果。

本站热点业务

更多模板/案例展示

关于我们 | 联系我们 | 团队日志 | 网站地图 | 网站合作