從零開始學正則(一)

 壹 ❀ 我爲什麼學正則

正則表達式是從側面衡量一個程序員水平的標準,可以很肯定的說沒有哪位高級開發不懂正則。在前端開發中使用正則表達式最頻繁的場景莫過於表單驗證,判斷郵箱,判斷手機號格式等等,那麼我是怎麼解決這些問題的呢,打開百度,輸入“正則驗證手機”回車,複製粘貼即可。我想大家應該看過不少關於常用正則整理的文章,我不理解正則,反正從來也記下來了。

古人云,熟讀唐詩三百首,不會做詩也會吟。我會花三週左右系統化學習正則表達式,若你有興趣可以與我一同學習(一起受苦),我相信學成之後即使無法立刻寫出逼格滿滿的正則,但對於閱讀大部分常見正則應該是沒問題的。

另外,正則學習系列文章均爲我閱讀 老姚《JavaScript正則迷你書》的讀書筆記,畢竟此時的我也只是一個學習者。這本書寫的真的超級棒,推薦大家下載在閒暇時間閱讀。

文中所有正則圖解均使用regulex製作。

最後偷偷說一句,公司前端組沒一個人懂正則,等我學會了我將是組裏第一個略懂正則的人!!

 貳 ❀ 正則兩種模糊匹配

正則表達式是一種匹配模式,要麼匹配字符(符合規則的字符),要麼匹配位置(符合規則字符所在的位置)。

正則之所以強大,是因爲正則能實現模糊匹配;在實際開發中,我們往往需要匹配某一範圍的數據。舉個貼切的例子,當驗證用戶輸入郵箱格式是否正確,除了“ @ ”等固定字符以外,用戶輸入的具體字符我們是無法估量和統計的,精準匹配顯得無能爲力,也只有模糊匹配能巧妙解決這個問題。

正則表達式的模糊匹配分爲橫向模糊縱向模糊兩種:

1.橫向模糊

其實不難理解,橫向模糊表示正則匹配的字符長度是不確定的,我們可以通過正則的量詞實現橫向匹配。不知道大家有沒有在B站看到過“233”的彈幕,“233”是一個網絡用語,表示大笑的意思。但因爲個人輸入隨心的習慣,可能打出2333,233333等不定長度的彈幕,那麼我們匹配彈幕中有多少233大笑可以用正則這麼寫:

var regex = /23{2,}/;

這裏量詞 {2,} 表示前面的3會出現2次或者更多次,量詞後面會專門介紹。我們來試試這個正則:

var regex = /23{2,}/g; 
var str = '223 233 2323 2333';
var result = str.match(regex);
result//["233", "2333"]

注意正則後面有個小寫的字母g,這是正則修飾符之一,g爲global的簡寫,表示全局匹配。若不加g,match方法只會匹配第一個符合條件的字符。關於修飾符後文會詳細介紹。

2.縱向模糊匹配

縱向模糊匹配是指具體某一位置可能有多種字符的情況,橫向模糊可以用量詞實現,而縱向模糊匹配可以使用字符組實現,比如:

var regex = /[abc]/;

這段正則表示可匹配字母a b c其中一個,我們來看一個簡單的例子:

var regex = /a[1-3]c/g;
var str = "a0c a1c a2c a3c a4c";
var result = str.match(regex);
result //["a1c", "a2c", "a3c"]

在這個例子中我們使用了字符組[1-3],它本質上與[123]效果相同,但因爲是連貫數字所以支持範圍簡寫。下面介紹具體介紹正則字符組。

 叄 ❀ 正則字符組

在上一個例子中我們已經瞭解到字符組[123]可用範圍表示法寫成[1-3],這是非常有用的,設想一想,我們現在想匹配數字1-9,字母a-f,要寫全的話就得這樣[123456789abcdef],但通過範圍表示法只用短短的[1-9a-f],是不是很奈斯:

現在知道了連字符 - 的作用,那麼現在我們就是要匹配1 - 3其中任意字符怎麼做呢?有三種寫法可解決這個問題,寫成[-13]、[13-]或者使用轉義符 \ 表示[1\-3]即可。

1.排除字符組

縱向模糊匹配還存在一種情況,就是某個位置可以是除了某幾個字符之外的任意字符,比如我希望是除了1-3之外的任意字符,那麼我們可以使用[^1-3]表示,注意這裏使用了脫字符 ^。

2.常用簡寫

瞭解了字符組範圍表示法,那麼想匹配數字0到9可以寫成[0-9],其實它還有一種更簡單的寫法\d,估計這部分是很多人常忘記的知識,我們來做個整理:

字符組 含義
\d [0-9]表示是一位數字,digit數字。
\D [^0-9]表示除數字以外的任意字符。
\w [0-9a-zA-Z_]表示數字,大小寫字母和下劃線,word簡寫,又稱單詞字符。
\W [^0-9a-zA-Z_],非單詞字符。
\s [ \t\v\n\r\f]表示空白符。包含空格,水平製表符,垂直製表符,換行符,回車符,換頁符。
\S [^ \t\v\n\r\f],非空白符。
. [^\n\r\u2028\u2029],通配符,表示除了換行符,回車符,行分隔符和段分隔符之外任意字符。

不懂就問,上述表格中空白字符都代表什麼意思?這裏我測試了下,可能因爲語言的問題,很多字符在js環境中沒法運行,C語言可以運行可惜我不懂...這裏我做了查閱做了整理:

空格:顧名思義,就是我們理解的空格

水平製表符\t:類似於tab鍵縮進的效果,一般系統中水平製表符佔8列,所以根據你按的次數佔據8*N列。

垂直製表符\v:讓文本從下一行開始輸出,且開始的列數爲\v前字符的後一列。

換行符\n:從下一行開頭開始輸出,這個js可以跑。

回車符\r:這裏的回車不是我們理解的enter回車另起一行開始輸出,而是回到當前行開頭輸出,還可能將已輸入文本替換,替換這一點根據環境不同表現不同。

換頁符\f:在輸出\f後面文本之前,會先將當前屏幕清空,類似於先清除再輸出。

行分隔符和段分隔符,找了一圈也沒看到好的解釋,這裏還望有緣人指點。

那麼如果我們想匹配任意字符,有這幾種寫法[/d/D]、[/w/W]、[/s/S]、[^],其實不難理解,以[/d/D]爲例,就是匹配數字以及除了數字以外的所有字符,這不就是所有字符了嗎。

 肆 ❀ 正則量詞

在講述正則橫向模糊匹配時已有使用量詞的例子,量詞表示某個字符的重複次數,我們也將常用量詞做個整理:
量詞 含義
{m,n} 至少出現m次,最多出現n次。
{m,} 至少出現m次,沒有上限。
{m} 等價於{m,m},固定出現m次
? 等價於{0,1},要麼不出現,要麼出現一次。
+ 等價於{1,},至少出現1次,沒有上限。
* 等價於{0,},表示出現任意次數,可以不出現,也可以任意次,包容性比?和+大。


1.貪婪匹配和惰性匹配

正則默認就是貪婪匹配,貪婪就是在量詞匹配規則範圍內最大限度的去匹配字符,我們來看個簡單的例子:

var str = "ab abb abbb abbbb abbbbb";
var regex = /ab{2,4}/g;
var result = str.match(regex);
result //["abb", "abbb", "abbbb", "abbbb"]

在這個例子中,我們匹配2-4個字母b,你給2個我要,給3個我要,哪怕給5個我也要盡我所能拿4個,是不是很貪心。

惰性與貪婪相反,惰性匹配就是在量詞匹配範圍內以最小限度去匹配字符,無慾無求做人本分,我們只需要在量詞後接個?即是惰性匹配,看個例子:

var str = "ab abb abbb abbbb abbbbb";
var regex = /ab{2,4}?/g;
var result = str.match(regex);
result //["abb", "abb", "abb", "abb"]

大家會不會覺得惰性匹配情況下這個次數4是不是沒意義了呢?其實並不是沒意義,儘管惰性匹配是以最小2次爲匹配規則,但被匹配的字符前提條件是滿足2-4之間,4還是起到了限制條件,我們改改例子再看:

var str = "abc abbc abbbc abbbbc abbbbbc";
var regex = /ab{2,4}?c/g;
var result = str.match(regex);
result //["abbc", "abbbc", "abbbbc"]

上述例子中當匹配到字段 abbbbbc 時因爲字母b已經超過範圍,所以不在匹配範圍內。惰性可以理解爲,在匹配範圍內拿最少的東西,我可以過的無慾無求,但也得過的溫飽活得下去纔行啊。

 伍 ❀ 正則多選分支

如果說橫向模糊匹配和縱向模糊匹配都是一種匹配模式,那如果需要同時使用多種模式怎麼辦呢,這裏我們就可以使用管道符 | 實現這一點,來看個簡單的例子:

var str = "a0c a1c a2c a3c abc abbc abbbc abbbbc";
var regex = /a[1-3]c|ab{1,3}c/g;
var result = str.match(regex);
result //["a1c", "a2c", "a3c", "abc", "abbc", "abbbc"]

在這個例子中,我們使用了縱向模糊匹配和橫向模糊匹配兩種模式。

需要注意的是,分支匹配也是惰性匹配,即前面的匹配模式能滿足,後面就不匹配了,來看個例子:

var str = "userName";
var regex = /user|userName/g;
var result = str.match(regex);
result //["user"]

這非常類似於js短路運算符中的||,以a||b爲例,倘若a爲真那麼b就不判斷了。

function fn1() {
  console.log(1);
  return true;
};

function fn2() {
  console.log(2);
  return true;
};
fn1() || fn2(); //1

我們再來個反轉,情理上來說分支匹配是惰性,但有一種特殊情況,直接上例子:

var str = "userName";
var regex = /Name|userName/g;
var result = str.match(regex);
result //["userName"]

哎?怎麼不是匹配Name字段,其實我也有這個疑問,去查了下也沒看到合理的解釋...我的猜測是,正則是從左往右的匹配機制,若左側一開始無法匹配成功(user和Name對應不上),則優先考慮了分支其它情況。

var str = "userName";
var regex = /Name/g;
var result = str.match(regex);
result //["Name"]

 陸 ❀ 總

那麼到這裏,JavaScript正則迷你書第一章節就看完了。我們做個總結,大家可以看着思維導圖回顧下知識點:

另外留兩個思考題,嘗試寫出匹配24小時制的正則匹配,以及匹配16進制顏色值的正則,注意,16進制顏色是支持#dddddd與#ddd兩種。

那麼到這裏本文結束,我也要抓緊時間看第二章節了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章