编程故事 2019-06-26
字母、数字、汉字、下划线以及一些没有特殊定义的标点符号,都属于普通字符,正则表达式中的普通字符匹配字符本身,如:
var str = "abced" console.log(str.match(/a/)) // ['a',index:0,input:'abced']
[]可以包含一系列字符,能够匹配其中的任意一个字符,如[abc]匹配abc中的任意一个字符,使用[^abc]包含的字符能够匹配除了abc以外的任何一个字符。比如以下含义:
var str = 'abcde' console.log(str.match(/[bcd][bcd]/)) // ['bc',index:1,input:'abcde']
如果需要匹配特殊字符本身,需要在特殊字符前加字符进行转义,比如需要表示.字符需要'\.'。
在正则表达式中,贪婪与非贪婪模式的区别主要是:被量词修饰的字表达式的匹配行为的不同,贪婪模式在整个表达式匹配成功的情况下尽可能多的匹配,非贪婪模式是在整个表达式匹配成功的前提下,尽可能少的匹配。
常见的修饰贪婪模式的量词如下:{x,y},{x,},?,,+。非贪婪模式就是在贪婪模式的量词后面加上一个?字符,就可以变成非贪婪模式的量词,比如:{x,y}?,{x,}?,??,?,+?。
var str = "longen<p>举杯邀明月、</p>yunxi<p>对影成三人</p>boyboy"; // 贪婪模式 console.log(str.match(/<p>.*<\/p>/)); // ['<p>举杯邀明月、</p>yunxi<p>对影成三人</p>',index:9,input:'...'] // 非贪婪模式 console.log(str.match(/<>.*?<\/p>/)); // ['<p>对影成三人</p>',index:9,input:'...']
在正则表达式匹配的过程中,如果子表达式匹配到的是字符内容,而非位置的话,并且被保存在匹配的结果中,那么就认为该子表达式是占有字符的,如果子表达式匹配的仅仅是位置,或者说匹配的内容不保存到匹配的结果中,那么该子表达式是零宽度的,比如环视。
比如上面的正则表达式/abc/,首先a取得控制权,从位置0开始匹配,a匹配成功,接着往下匹配,把控制权交给b,从位置1开始匹配,直到整个正则表达式匹配完成。对于含有量词?正则表达式/ab?c/,量词?是贪婪模式,会优先选择尽可能多的匹配修饰的字符,所以该表达式会优先选择匹配一个b字符,当b匹配不到时再选择不匹配b字符。对于含有量词的??的正则表达式,量词??是非贪婪模式,会优先选择尽可能少的匹配修饰的字符,所以该表达式会优先不匹配b字符,比如对于字符串abc,a先匹配,控制权交给b??,该表达式先不匹配字符b,控制权交给c,c不能匹配abc中的b,匹配失败进行回溯,由b??表达式匹配字符b,再把控制权交给c,匹配成功,因此整个表达式都匹配成功。
环视是只进行子表达式的匹配,不占有字符,匹配的内容不保存到最终的匹配结果,是零宽度的,它匹配的结果就是一个位置,环视的作用相当于对所在的位置加一个附加条件,只有满足了这个条件,环视表达式才能匹配成功,环视有顺序和逆序两种,顺序和逆序又分为肯定和否定,但是在JavaScript中只支持顺序环视,下面就是顺序环视的匹配过程:
(?=Expression)是顺序肯定环视,含义是在位置的右侧有expression。(?!Expression)是顺序否定环视,含义是在位置的右侧没有expression。如下图所示:该正则表达式的含义是:以字母或者数字组成的,并且第一个字符必须为小写字母开头。其匹配过程如下:
首先元字符^取得控制权,匹配字符串开始位置,接着将控制权交给顺序肯定环视表达式(?=[a-z]),它的含义是在位置0的右侧是有a-z的小写字母,在这里匹配成功,将控制权交给[a-z0-9]+表达式,由于(?=[a-z])是零宽度的,字符串已匹配的位置不会增加,因此对于[a-z0-9]+其匹配的起始位置仍然为0。
对于顺序否定环视,是根据其顺序肯定环视的匹配结果来推断的,当顺序肯定环视匹配成功,顺序否定就失败,顺序肯定匹配失败,顺序否定就成功。比如对于正则表达式/<(?!/?p\b)[^>]+>/,其含义是匹配所有非<p>的标签,首先顺序否定环视(?!/?p\b)要转换成(?=/?p\b),当(?=/pb)匹配失败时,子表达式(?!/?p\b)才匹配成功。
捕获组就是把正则表达式中用()包裹的子表达式匹配的内容,保存到内存中以数字编号或显示命名的组里,方便以后使用,捕获组的编号规则,编号是按照"("出现的顺序,从左到右,从1开始进行编号,比如如下代码:
console.log(/(longen)(123)/.test("longen123longen")) console.log(RegExp.$1,RegExp.$2) // longen 123 // 使用replace替换,将分组内容替换 var num = "11 22" var n = num.replace(/(\d+)\s*(\d+)/,"$1 $2") console.log(n) // 22 11
非捕获性分组:子表达式可以作为被整体修饰但是子表达式匹配的结果不会被存储,要创建一个非捕获性分组,只需要在“(”字符的后面加上“?:”就可以了,比如:(?:Expression)。
捕获性分组取到的内容,不仅可以在正则表达式外部通过程序进行引用,也可以在正则表达式内部进行引用,这种引用方式叫做反向引用。捕获性分组的反向引用的写法如:number。
反向引用的匹配原理:捕获分组在匹配成功以后,会将子表达式匹配到的内容,保存到内存中一个以数字编号的组里,可以简单的认为是对一个局部变量进行了赋值,这时可以通过反向引用,引用这个局部变量的值,一个捕获分组在匹配成功之前,它的内容是不确定的,一旦匹配成功,它的内容就确定了,反向引用的内容也就确定了。比如以下内容:
var str = "longaabbcd" console.log(str.match(/([ab])\1/)) ['aa',...]
对于表达式([ab]),可以匹配a,也可以匹配b,但是如果匹配成功,它的反向引用也就确定,如果捕获分组匹配到的是a,那么反向引用也就只能匹配a,也就是说([ab])1只能匹配aa或者bb。