这几日看权威指南,对正则里的分组、引用和断言有了更深的理解,特地整理一下加深印象。
为了详细地解释,首先将权威指南第6版上相关描述的原文贴出来,重点用红色标识。
字符 | 含义 |
---|---|
(......) | Grouping. Group items into a single unit that can be used with *, +, ?, | , and so on. Also remember the characters that match this group for use with later references. |
(?:...) | Grouping only. Group items into a single unit, but do not remember the characters that match this group. |
(?= p ) | A positive lookahead assertion. Require that the following characters match the pattern p, but do not include those characters in the match. |
(?! p ) | A negative lookahead assertion. Require that the following characters do not match the pattern p. |
一、分组
(......)有多种作用,一种是把单独的项进行组合,将括号内的项作为一个独立的单元来处理(使用*, +, ?, | , etc),举个例子:
//?表示匹配前一项0次或1次
//\s是指任何Unicode空白字符
var reg = /hello(\sworld)?/; //如果world是一个可选项,采用这种方式就可以进行选择
var str1= "hello";
var str2 = "hello world";
reg.test(str1); //true
reg.test(str2); //true
另一个作用就是在完整的模式中定义子模式,这样我们可以将每个圆括号中子模式匹配出来的结果提取出来,上例子:
var str = "abc123"; //我们匹配的是一个或多个字母后面加一个或多个数字,但是实际上我们只对其中的字母感兴趣 var reg1 = /[a-z]+\d+/; //未进行分组 var reg2 = /([a-z]+)\d+/; alert(str.match(reg1)); //abc123;只会输出整个匹配正确的字符 alert(str.match(reg2)); //abc123,abc;子表达式匹配的结果也输出 alert(str.match(reg2)[1]); //abc;通过[]的选取方式将所需内容提取出来,如果用firebug控制台可以看到相应的index
如果对match的用法不了解请参考http://www.w3school.com.cn/jsref/jsref_match.asp
二、选择
(......)还有个作用就是允许在同一表达式的后部引用前面的子表达式,通过"\n"的方式实现,这里的n指的是带圆括号的子表达式在正则表达式中的位置。因为子表达式是可以嵌套的,所以它的位置是参与计数的左括号的位置。注意:此处的引用并不是对子表达式模式的引用,而是指与那个模式相匹配的文本的引用。
也就是说/([a-z]+)\d\1/如果对"abc123"进行匹配,实际上可以看做是/([a-z]+)\dabc/这样一种形式。
这里举个书上的例子:
/['"][^'"]*['"]/; //这个表达式匹配的是位于单引号或双引号内的任意个字符,但是并不要求左侧和右侧的引号相匹配 //如果要左右两边的引号是匹配的,可以这样写: /(['"])[^'"]*\1/; //如果(['"])匹配到的是',那么\1就是\',这样能保证两边都是一样的符号
上面为什么要说参与计数的左括号呢?正是因为还有不参与的家伙在,就是(?:),在前面的表格也说了,它是grouping only,就是说它只组合,至于匹配的字符它不记忆,更谈不上分组了,比如说/(?:hello)\s(world)/,这里\1引用的就是与(world)相匹配的字符。
三、断言
断言,就是指明某个字符串前边或者后边,将会出现满足某种规律的字符串。以我的理解,比如说:我要找红色的苹果,这个红色的就是一种断言,虽然我要找是苹果,但是它首先得是红色的。
js中只有先行断言,(?=)是零宽正向先行断言,要求接下来的字符都与p匹配,但不能包括匹配p的那些字符;(?!)是零宽负向先行断言,要求接下来的字符不与p匹配。
先来看一下(?=p)是怎么工作的,/Windows (?=95|98|NT|2000)/ 能匹配 "Windows2000" 中的 "Windows" ,但不能匹配 "Windows3.1" 中的 "Windows"。
也就是说,它要求Windows后面必须是95|98|NT|2000中的一个,而在匹配正确之后,它的返回值中并不包括95|98|NT|2000的部分:
var reg = /Windows(?=95|98|NT|2000)/; var str1 = "Windows2000"; var str2 = "Windows3.1"; alert(str1.match(reg)); //Windows reg.test(str1); //true reg.test(str2); //false
这个还是很好理解的,但是我想试一下的时候就遇到问题了。
//这里我想匹配类似#userName的结构,也就是/\#\w+/这样的形式; //但是呢,我只想提取出userName这一部分,当然这用分组就可以实现了,不过这里还是用这个举下例子 //首先我是这样写的 var reg = /(?=\#)\w+/; console.log(reg.test("#cccc")); //false
为啥会是false呢,这就是零宽的概念了,就是只匹配位置,在匹配过程中,不占用字符,所以被称为“零宽”,这种方式也叫非获取性匹配,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索, 而不是从包含预查的字符之后开始。
在上面的例子中,截止到(?=\#)匹配到的是“右边是#的位置”,也就是#的左边,接下来并不是从u开始匹配,而是从#开始,#当然不是\w了所以结果是false,换成/(?=\#)\#w+/结果就是true了。
关于(?!)再举个例子:
var str = "#www#eee"; var reg1 = /(?!\#)\w+/g; var reg2 = /\w+(?!\#)/g; console.log(str.match(reg1)); //["www", "eee"] console.log(str.match(reg2)); //["ww", "eee"]
解释:
/(?!\#)\w+/g,截止到(?!\#)匹配的是“右边不是#”的位置,也就是#的右边,从这里开始匹配后边的表达式。第二个表达式也是按同样的方式理解。
补充:
谢谢一楼提醒,这里补充一下es6新增的后行断言
其实也很好理解:
代码如下
var str = '#username' // 后行断言 var reg1 = /(?<=#)\w+/ str.match(reg1) // ['username'] // 后行否定断言 var reg2 = /(? str.match(reg2) // ['sername']
以上是我对正则里这几个概念的理解,可能不是很准确,若有错误欢迎指出。