近几日对本身一直不太擅长的正则表达式作了一次全面的扫盲。心疼本身之余仍是有一些收获吧,在这里作一个比较零散的总结,整理一些对理解正则比较有利的点。
你没有看错,就是黑人问号中的问号,这个字符在正则里面算是一个入门中很容易被带偏的点了。首先要知道什么是正则中的量词。node
在正则中,一般要表示一个表达式匹配的数量,这个时候量词就登场了。
主要会使用如下几个量词正则表达式
/(\w)*/.exec(str) // 匹配任意次 /(\w)+/.exec(str) // 匹配一次到屡次 /(\w)?/.exec(str) // 匹配零到一次(记住这里问号的用法!) /(\w){2, 4}/.exec(str) // 匹配两次到四次 /(\w){2, }/.exec(str) // 匹配两次以上
咱们能够发现,在这里"?"做为一个量词来使用,表示匹配零到一次。chrome
接下来要理解下一个概念:贪婪匹配code
搜了一下wiki,貌似没有相关的词条,通俗的解释,贪婪匹配模式下,会尽量多地匹配知足条件的字符。而正则默认是贪婪模式的。ci
举个例子。好比我要匹配"suuuuuuuuuuck"字符中的s和k中间的字符。并无什么问题。unicode
let result = "suuuuuuuuuuck".match(/s(.*)k/)[1] // uuuuuuuuuuc
可是我如今要搞事情,要你在"suuuuuuuuuuck duck"字符串中匹配相同的字段,一样的表达式会匹配到如下的结果,由于此时的正则是贪婪的,它必定会匹配到没法匹配的时候才休止。字符串
// uuuuuuuuuuck duc
这时候就须要手动开启非贪婪模式了get
let result = "suuuuuuuuuuck duck".match(/s(.*?)k/)[1] // uuuuuuuuuuc
区别是在量词后加了个问号,这时候该捕获组就算是开启了非贪婪模式了。input
按照上面的理解,若是你是一个新手,确定会有所疑惑,量词(*)后面跟着一个量词(?),这是什么鬼意思,这么反人类的?产品
其实,这里就涉及到"?"的第二个用法了,即当它跟在一个量词背后的时候,表示该表达式开启了非贪婪模式,即对表达式尽量少地匹配结果。因此,对应的,配合量词使用,会产生如下结果。
思考题:因此,"??" 应该如何匹配呢?
正则匹配除了验证一个字符串是否符合条件外,还能够从中提取咱们所须要的信息。好比,咱们获得了一个"新中国成立于1949-10-01"的字符串,做为一个爱国人士,咱们要把这个年月日提取出来谨记于心。因此我写了一个正则,得到的结果以下
这里提取的操做就须要经过小括号进行捕获。正则会默认对捕获组分配组数。
"新中国成立于1949-10-01".match(/(\d{4})-(\d{2})-(\d{2})/) // ["1949-10-01", "1949", "10", "01", index: 6, input: "新中国成立于1949-10-01", groups: undefined]
咱们也能够忽略某些分组"(?:exp)",这样正则就不会为为其分配组数。
"新中国成立于1949-10-01".match(/(\d{4})-(\d{2})-(?:\d{2})/) // ["1949-10-01", "1949", "10", index: 6, input: "新中国成立于1949-10-01", groups: undefined]
假如咱们有一个叠词判断的需求,验证一个词语是否是"ABA"格式的,咱们能够这么作
// 首先汉字的unicode范围是\u4e00-\u9fa5 // 这里咱们首先对第一个字符进行了捕获,组数为1 // 而后咱们后面经过"\1"的方式去复用捕获组,这样就意味着匹配到了相同的字符,也就达到了限制的目的。 /([\u4e00-\u9fa5])[\u4e00-\u9fa5]\1/.test("是否是") // 固然是true
要记住下标对人类来讲仍是挺麻烦的,能够说彻底没啥可读性,固然正则也提供了为分组命名的方式
"新中国成立于1949-10-01".match(/(?<year>\d{4})-(?<month>\d{2})-(?<date>\d{2})/) // 咱们能够发现,这时候捕获组不只拥有组数,同时groups属性不为空了。 // ["1949-10-01", "1949", "10", "01", index: 6, input: "新中国成立于1949-10-01", groups: {…}] // 展开groups 是这样的 // {year: "1949", month: "10", date: "01"} /** 固然命名捕获组也是可使用的 */ // (?<name>exp) 命名捕获组 // \k<name> 引用 // 仍是叠词的那个例子 /(?<thx>[\u4e00-\u9fa5])[\u4e00-\u9fa5]\k<thx>/.test("是否是")
如今有一个需求,匹配出英文语句"I'm singing while you're dancing"中全部带有ing后缀的单词(不包含ing)。要想拿到danc 和 sing,咱们须要用到零宽断言。
零宽断言用于查找某些内容以前或以后的东西,只指定一个位置,自己并不占据字符,这也是为何咱们称之为零宽度
对于表达式表示确定,咱们称之为正向,反之称之为负向,(注意,这里的正负指的是对条件的确定和否认,而不是匹配的方向。)
而对于匹配的方向而言,咱们有另一种称呼,对向后匹配的称之为预测先行,向前匹配的称之为回顾后发
因此,对应的四种组合分别是
目前的js引擎对回顾后发断言的实现还不彻底,就我所知在chrome能成功使用,可是在nodejs环境下是不识别的。
如今咱们从引言中的例子来实践一下
"I am singing while you're dancing".match(/\b([a-zA-Z]+)(?=ing\b)/g) // 咱们忽略前面不知足的匹配,直到index = 4时,s为单词边界,知足条件 // 而第一个捕获组是贪婪的,他会首先匹配到整个singing,而后将掌控权交给(?=ing\b),singing不知足匹配 "singinging" // 因而开始回溯到单词 singin,继续断言, 匹配到的下一个字符为"g", 不知足"singining", 又开始回溯到"singi"... // 直到回溯到"sing"时,断言后面有一个ing,而且是一个单词边界,因而"singing"知足条件,这时候咱们的正则匹配到了第一个结果。 // 因为零宽断言是不消费字符的,因此咱们获得整个表达式匹配的第一个结果是"sing" // 因而引擎以一样的方式向后面的位置查找,获得了danc // ["sing", "danc"]
咱们如今看一下怎么使用负向断言,假如咱们有一个系统,3月25号要进行维护,不能使用了,这时候有用户要办理业务,选择日期的时候咱们要过滤3月25日这一天,因此产品经理要你临时加上一条规则限定。
选择后日期输出的格式是"yyyy-mm-dd",这时候咱们能够这么写正则
/(?!2018-03-25)(\d{4})-(\d{2})-(\d{2})/.test("2018-03-11") // true 经过验证 /(?!2018-03-25)(\d{4})-(\d{2})-(\d{2})/.test("2018-03-25") // false
用(?<=exp) 找出 "beep name=wanglihong abcdefg"
"beep name=wanglihong abcdefg".match(/(?<=\bname=)(\w+\b)/) // ["wanglihong", "wanglihong", index: 10, input: "beep name=wanglihong abcdefg", groups: undefined]
提取a标签的属性的同时,经过(?<!exp) 过滤style属性
var template = '<a href="/bee" target="_blank" id="o" style="color: black;">点击跳转</a>' template.match(/(\w+)=(?<!style=)"([^"]+)"/g) // [href="/bee", target="_blank", id="o"]
摸透了零宽断言,正则的能力也就算上了一个台阶了,固然还有平衡组这种操做,由于在js不支持,因此就暂时不讨论了。