正则表达式分组、引用和断言

这几日看权威指南,对正则里的分组、引用和断言有了更深的理解,特地整理一下加深印象。

为了详细地解释,首先将权威指南第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 = /(?<!#)\w+/

str.match(reg2) // ['sername']

 

 

 以上是我对正则里这几个概念的理解,可能不是很准确,若有错误欢迎指出。

posted @ 2017-05-04 22:27  阿萌ameng  阅读(1553)  评论(2编辑  收藏  举报