前言
倆件事促使我寫這篇文章。一則之前面試面試到了正則,發(fā)現(xiàn)不甚熟悉。二則這些天看jQuery源碼,一些正則看的有點費勁。
準備
先說些題外話。什么是正則表達式?其實正則表達式也叫規(guī)則表達式。個人感覺后者比較貼切。也很納悶Regular Expression為什么會翻譯成正則表達式。知道的朋友可以告知下。
其實這個正則就是字符串匹配工具,它描述了字符串的規(guī)則。無論是初學者還是老司機相信寫起來都費勁,因為是火星文啊有木有。那為什么要學習這個火星文呢?那自然是好處大大啊。
舉個栗子。前倆天解單時有個情況是需要對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代表數(shù)字。
常用元字符
| 代碼 | 說明 |
|---|---|
| . | 匹配除換行符以外的任意字符 |
| \w | 匹配字母或數(shù)字或下劃線 |
| \s | 匹配任意的空白符 |
| \d | 匹配數(shù)字 |
| \b | 匹配單詞的開始或結(jié)束 |
| ^ | 匹配字符串的開始(值得注意的是,它在[]里面指的是非,在外面指的是字符串開始,還可以用在零寬斷言) |
| $ | 匹配字符串的結(jié)束 |
反義詞
常用反義詞
可以看得出好些就是元字符大寫就是其對應的負面。每一對可匹配全部。譬如\w\W
| 代碼/語法 | 說明 |
|---|---|
| \W | 匹配任意不是字母,數(shù)字,下劃線,漢字的字符 |
| \S | 匹配任意不是空白符的字符 |
| \D | 匹配任意非數(shù)字的字符 |
| \B | 匹配不是單詞開頭或結(jié)束的位置 |
| [^x] | 匹配除了x以外的任意字符 |
| [^aeiou] | 匹配除了aeiou這幾個字母以外的任意字符 |
限定符
什么是限定符呢?舉個栗子吧。街上很多人。這個人呢可以是元字符,而這個許多就是限定符。和元字符或者表達式模式結(jié)合使用
常用限定符
| 代碼/語法 | 說明 |
|---|---|
| * | 重復零次或更多次(相當于{0,}) |
| + | 重復一次或更多次(相當于{1,}) |
| ? | 重復零次或一次(相當于{0,1}) |
| {n} | 重復n次 |
| {n,} | 重復n次或更多次 |
| {n,m} | 重復n到m次 |
轉(zhuǎn)義
如果有時候你要匹配譬如.、*時會發(fā)現(xiàn)不能匹配,這是因為它們在正則里有其特殊含義(元字符等)。此時就需要轉(zhuǎn)義。即.和*。
eg. https:\\baidu.com就是https:\baidu.com(瞎寫o(╯□╰)o)
類與組以及分支
比如\d匹配數(shù)字,已經(jīng)圈死為數(shù)字類。但是比如現(xiàn)在我想匹配自定義的類別,比如我想看看有沒有1、2、3之中任何字符那就是[123]。比如[0-9]其實和/d一樣。[]就是類,但是可以發(fā)現(xiàn)它只是單個字符,所以也就是字符類。
但是現(xiàn)在我想匹配多字符,那就是組了。比如我要匹配yellow、green、red。(yellow|green|red)。這()就是組,后面接限定符可以重復這個組,用法和元字符類似。|就是分支,也就是或的意思。
這里可能有點亂。給個小栗子總結(jié)下:
怎么匹配國內(nèi)的手機號呢?咋們先想想手機號的規(guī)則。
- 手機號一共11位數(shù)字
- 前兩位只能是13、14、15、18
- 有座機的朋友應該知道打外省的手機號需要加撥0(至少我小時候是這樣子的_)
自然而然就得出答案:0?(13|14|15|18)[0-9]{9}
自此,差不多可以應付大多數(shù)工作上簡單的情況(工作上真心用得少,以為你會發(fā)現(xiàn)你用的復雜還怕別人看的痛苦,注釋都不好寫( ˇ?ˇ )。更郁悶的是過幾天自己都看不懂)
進階
現(xiàn)在開始講些難些的,用于應付閱讀源碼。
反向引用、捕獲組/非捕獲組
先舉個小例子。比如給定一個字符串,我想先匹配一下一個不定的單詞,然后此單詞之后得跟著相同的單詞。可能有點繞。就是比如前面匹配到了pz,那么緊跟著也得是pz,即pz pz。如果匹配到的時ap,那么緊跟著的也得是ap,即ap ap。
這時候就需要反向引用了。先寫前面的匹配,即\b(\w+)\b\s+,之后怎么寫呢?很簡單:\b(\w+)\b\s+\1\b。這里的\1就是對前面的那個組的引用。
為什么時\1呢?其實每個組都是會分配到一個組號。整個正則表達式匹配到的為分組0,然后從左往右掃,依次分組1、2遞增。注意,順序看(,不要被數(shù)學里的表達式優(yōu)先級給帶入坑。比如:(1(2)(3)),順序如此1、2、3,并非是2、3、1這個順序
有個情況需要注意的是,正則不是某個語言獨有的,所以不同語言的支持情況可能有差異。
PS: JS是不支持給組自定義分配組號的,C#就可以。有興趣的可以了解了解。對了,若是有自定義組號,分配組號時會掃描倆遍。第一遍是給未自定義組號的組分配組號,第二遍給自定義的組分配組號。
如果組過多的話我不想某些組被捕獲,那么可以使用?:。即捕獲組為(exp),非捕獲組為(?:exp)。非捕獲組是不會被分配組號的。也不被捕獲文本。(字符串match返回值不包括非捕獲組)
| 代碼/語法 | 說明 |
|---|---|
| (exp) | 捕獲組:匹配exp,會被分配組號的,被捕獲文本 |
| (?:(exp) | 非捕獲組:匹配exp,不會被分配組號的,也不被捕獲文本 |
有圖有真相時間(給本小節(jié)所說證明一下)


零寬斷言(zero-width assertion)
先行斷言、先行否定斷言(JS支持)

好吧,這就是我第一次知道這貨的表情。什么鬼?已經(jīng)很難明白了,現(xiàn)在連命名都這樣子?怎么活。好吧,不急,其實這貨意義就是指定位置應該滿足指定的條件。還是很無語?

- 比如匹配小數(shù)的整數(shù)部分,即:/\d+(?=.)/。
(?=exp)就是正預測先行斷言,也叫先行斷言,只匹配exp前面的位置,斷言自身位置后面跟著exp
可以看見的是先行斷言是不會被匹配到的,這就是零寬。它只是告訴你是否匹配成功,它不消費任何字符。 - 除了先行斷言,還有先行否定斷言,也就是負預測先行斷言。這貨是先行斷言的否定,看名字就看得出來。
比如匹配后面沒有小數(shù)點的數(shù)字,即:/\d+(?!.)/g
(?!exp)就是負預測先行斷言,也叫先行否定斷言,只匹配后面沒有exp的位置,斷言自身后面沒有exp
后行斷言、后行否定斷言(目前不支持,但將支持)
其實也不能說不支持,至少此時我試了下我的Chrome(版本 62.0.3202.94)是支持的,不過Firefox是不支持的。
- 比如要匹配小數(shù)的小數(shù)部分,即/\d+(?<=.)/
(?<=exp)就是后行斷言,也叫正回顧后發(fā)斷言,只匹配前面有exp的位置,斷言自身前面有exp
- 比如匹配前面沒有小數(shù)點的數(shù)字,即:/(?<!exp)\d+/
(?<!exp)就是后行否定斷言,也叫負回顧后發(fā)斷言,只匹配前面沒有exp的位置,斷言自身前面沒有exp
正則的性情(貪婪模式、懶惰模式)
正則默認情況下是貪婪的。就比如爬過小說的朋友應該知道。不是所有的網(wǎng)站都是通過API請求數(shù)據(jù)填充網(wǎng)頁的。很多網(wǎng)站是服務端渲染,爬的時候就只能獲取到整個HTML網(wǎng)頁。這時候就需要使用正則把html標簽去掉。
有了上面的知識你很快就會寫出來: /<.+>/g。字符串replace方法替換成空格即可。很遺憾,這個是不行的,為什么呢?因為正則貪婪
<main>
<header>斗破蒼穹</header>
<article>炎帝</article>
<footer>五帝破空</footer>
</main>
//這里會匹配到整個html,因為你會發(fā)現(xiàn)整個html也符合你寫的這個表達式呀
這時候機智的你可能會想到,<>里面是不能有<>的,那我可以這樣子:/<[^<>]+>/g。
是的,的確可行。但是你至少得想到這個特殊情況吧,難道以后每次都得想下,很是麻煩啊。其實有簡便的方法,那就是限定符后面加個?。即:/<.+?>/g,這就是懶惰模式
很好理解,就是匹配到就不繼續(xù)了。?本來就是代表0-1嘛(強行解釋一波O(∩_∩)O哈哈~)

PS: 所以有*?、+?、??、{n, m}?、{n, }?。是沒有{n}?滴
修飾符
為什么修飾符放在最后說呢?因為它最不起眼。最簡單。
就是加在正則最后面。
| 修飾符 | 描述 |
|---|---|
| i | ignoreCase執(zhí)行對大小寫不敏感的匹配。 |
| g | global執(zhí)行全局匹配(查找所有匹配而非在找到第一個匹配后停止) |
有興趣可以自行看下多行模式,這里不提。
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
匹配到的話以數(shù)組形式返回結(jié)果,成員是每一個匹配到的串,否則返回null
后記
最后來個考試。從jQuery拷了個正則解讀下
/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/
我看懂了,你們呢?




