前言
俩件事促使我写这篇文章。一则之前面试面试到了正则,发现不甚熟悉。二则这些天看jQuery源码,一些正则看的有点费劲。
准备
先说些题外话。什么是正则表达式?其实正则表达式也叫规则表达式。个人感觉后者比较贴切。也很纳闷Regular Expression为什么会翻译成正则表达式。知道的朋友可以告知下。
其实这个正则就是字符串匹配工具,它描述了字符串的规则。无论是初学者还是老司机相信写起来都费劲,因为是火星文啊有木有。那为什么要学习这个火星文呢?那自然是好处大大啊。
举个栗子。前俩天解单时有个情况是需要对location.hash解析出#后面的第一个字符串(不包括/)。譬如
//"#/notebooks/17855961"解析出来的得是notebooks
// “#notebooks/17855961”解析出来的得是notebooks
function parseHash(str) {
var hashArr = str.split('/');
var res;
if (hashArr[0] === '#') {
res = hashArr[1];
} else {
res = hashArr[0].substring(1);
}
return res;
}
function parseHash(str) {
var reg = /(?<=^(?:#\/|#))[^\/]+?(?=\/)/;
return str.match(reg)[0];
}
其实,不用正则也是可以的,当然你得像上面一样写一大串代码。
基础
元字符
其实就是正则里具有特殊含义的字符。就比如\d代表数字。
常用元字符
代码 | 说明 |
---|---|
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\b | 匹配单词的开始或结束 |
^ | 匹配字符串的开始(值得注意的是,它在[]里面指的是非,在外面指的是字符串开始,还可以用在零宽断言) |
$ | 匹配字符串的结束 |
反义词
常用反义词
可以看得出好些就是元字符大写就是其对应的负面。每一对可匹配全部。譬如\w\W
代码/语法 | 说明 |
---|---|
\W | 匹配任意不是字母,数字,下划线,汉字的字符 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结束的位置 |
[^x] | 匹配除了x以外的任意字符 |
[^aeiou] | 匹配除了aeiou这几个字母以外的任意字符 |
限定符
什么是限定符呢?举个栗子吧。街上很多人。这个人呢可以是元字符,而这个许多就是限定符。和元字符或者表达式模式结合使用
常用限定符
代码/语法 | 说明 |
---|---|
* | 重复零次或更多次(相当于{0,}) |
+ | 重复一次或更多次(相当于{1,}) |
? | 重复零次或一次(相当于{0,1}) |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
转义
如果有时候你要匹配譬如.、*时会发现不能匹配,这是因为它们在正则里有其特殊含义(元字符等)。此时就需要转义。即.和*。
eg. https:\\baidu.com就是https:\baidu.com(瞎写o(╯□╰)o)
类与组以及分支
比如\d匹配数字,已经圈死为数字类。但是比如现在我想匹配自定义的类别,比如我想看看有没有1、2、3之中任何字符那就是[123]。比如[0-9]其实和/d一样。[]就是类,但是可以发现它只是单个字符,所以也就是字符类。
但是现在我想匹配多字符,那就是组了。比如我要匹配yellow、green、red。(yellow|green|red)。这()就是组,后面接限定符可以重复这个组,用法和元字符类似。|就是分支,也就是或的意思。
这里可能有点乱。给个小栗子总结下:
怎么匹配国内的手机号呢?咋们先想想手机号的规则。
- 手机号一共11位数字
- 前两位只能是13、14、15、18
- 有座机的朋友应该知道打外省的手机号需要加拨0(至少我小时候是这样子的_)
自然而然就得出答案:0?(13|14|15|18)[0-9]{9}
自此,差不多可以应付大多数工作上简单的情况(工作上真心用得少,以为你会发现你用的复杂还怕别人看的痛苦,注释都不好写( ˇˍˇ )。更郁闷的是过几天自己都看不懂)
进阶
现在开始讲些难些的,用于应付阅读源码。
反向引用、捕获组/非捕获组
先举个小例子。比如给定一个字符串,我想先匹配一下一个不定的单词,然后此单词之后得跟着相同的单词。可能有点绕。就是比如前面匹配到了pz,那么紧跟着也得是pz,即pz pz。如果匹配到的时ap,那么紧跟着的也得是ap,即ap ap。
这时候就需要反向引用了。先写前面的匹配,即\b(\w+)\b\s+,之后怎么写呢?很简单:\b(\w+)\b\s+\1\b。这里的\1就是对前面的那个组的引用。
为什么时\1呢?其实每个组都是会分配到一个组号。整个正则表达式匹配到的为分组0,然后从左往右扫,依次分组1、2递增。注意,顺序看(,不要被数学里的表达式优先级给带入坑。比如:(1(2)(3)),顺序如此1、2、3,并非是2、3、1这个顺序
有个情况需要注意的是,正则不是某个语言独有的,所以不同语言的支持情况可能有差异。
PS: JS是不支持给组自定义分配组号的,C#就可以。有兴趣的可以了解了解。对了,若是有自定义组号,分配组号时会扫描俩遍。第一遍是给未自定义组号的组分配组号,第二遍给自定义的组分配组号。
如果组过多的话我不想某些组被捕获,那么可以使用?:。即捕获组为(exp),非捕获组为(?:exp)。非捕获组是不会被分配组号的。也不被捕获文本。(字符串match返回值不包括非捕获组)
代码/语法 | 说明 |
---|---|
(exp) | 捕获组:匹配exp,会被分配组号的,被捕获文本 |
(?:(exp) | 非捕获组:匹配exp,不会被分配组号的,也不被捕获文本 |
有图有真相时间(给本小节所说证明一下)
零宽断言(zero-width assertion)
先行断言、先行否定断言(JS支持)
好吧,这就是我第一次知道这货的表情。什么鬼?已经很难明白了,现在连命名都这样子?怎么活。好吧,不急,其实这货意义就是指定位置应该满足指定的条件。还是很无语?
- 比如匹配小数的整数部分,即:/\d+(?=.)/。
(?=exp)就是正预测先行断言,也叫先行断言,只匹配exp前面的位置,断言自身位置后面跟着exp
可以看见的是先行断言是不会被匹配到的,这就是零宽。它只是告诉你是否匹配成功,它不消费任何字符。 - 除了先行断言,还有先行否定断言,也就是负预测先行断言。这货是先行断言的否定,看名字就看得出来。
比如匹配后面没有小数点的数字,即:/\d+(?!.)/g
(?!exp)就是负预测先行断言,也叫先行否定断言,只匹配后面没有exp的位置,断言自身后面没有exp
后行断言、后行否定断言(目前不支持,但将支持)
其实也不能说不支持,至少此时我试了下我的Chrome(版本 62.0.3202.94)是支持的,不过Firefox是不支持的。
- 比如要匹配小数的小数部分,即/\d+(?<=.)/
(?<=exp)就是后行断言,也叫正回顾后发断言,只匹配前面有exp的位置,断言自身前面有exp
- 比如匹配前面没有小数点的数字,即:/(?<!exp)\d+/
(?<!exp)就是后行否定断言,也叫负回顾后发断言,只匹配前面没有exp的位置,断言自身前面没有exp
正则的性情(贪婪模式、懒惰模式)
正则默认情况下是贪婪的。就比如爬过小说的朋友应该知道。不是所有的网站都是通过API请求数据填充网页的。很多网站是服务端渲染,爬的时候就只能获取到整个HTML网页。这时候就需要使用正则把html标签去掉。
有了上面的知识你很快就会写出来: /<.+>/g。字符串replace方法替换成空格即可。很遗憾,这个是不行的,为什么呢?因为正则贪婪
<main>
<header>斗破苍穹</header>
<article>炎帝</article>
<footer>五帝破空</footer>
</main>
//这里会匹配到整个html,因为你会发现整个html也符合你写的这个表达式呀
这时候机智的你可能会想到,<>里面是不能有<>的,那我可以这样子:/<[^<>]+>/g。
是的,的确可行。但是你至少得想到这个特殊情况吧,难道以后每次都得想下,很是麻烦啊。其实有简便的方法,那就是限定符后面加个?。即:/<.+?>/g,这就是懒惰模式
很好理解,就是匹配到就不继续了。?本来就是代表0-1嘛(强行解释一波O(∩_∩)O哈哈~)
PS: 所以有*?、+?、??、{n, m}?、{n, }?。是没有{n}?滴
修饰符
为什么修饰符放在最后说呢?因为它最不起眼。最简单。
就是加在正则最后面。
修饰符 | 描述 |
---|---|
i | ignoreCase执行对大小写不敏感的匹配。 |
g | global执行全局匹配(查找所有匹配而非在找到第一个匹配后停止) |
有兴趣可以自行看下多行模式,这里不提。
RegExp对象
既然是前端,自然得来点前端的
属性
其实上面的修饰符也是RegExp属性,比如
/\d/g.ignoreCase // false
/\d/g.global // true
与修饰符无关的主要有俩
- source,返回正则表达式字符串形式
/^\d\\\.[0-9]$/g.source // "^\d\\\.[0-9]$"
-
lastIndex,返回下一次开始搜索的位置,它是可读写的,注意只有带g修饰符才有意义
方法
- test
返回true or false,表示当前表达式能否匹配当前字符串 - exec
匹配到的话以数组形式返回结果,成员是每一个匹配到的串,否则返回null
后记
最后来个考试。从jQuery拷了个正则解读下
/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/
我看懂了,你们呢?