正規表示式系列之初級入門篇

黃Java發表於2018-07-09

概述

本文主要通過對正規表示式的語法進行一些簡單的介紹,從而讓沒有接觸過或者想學習正規表示式的同學有一個基礎的瞭解,從而能夠看懂和編寫使用一般的正規表示式。

本文的主要內容為:

  • 正規表示式的字元匹配
  • 正規表示式的位置匹配
  • 正規表示式的括號與捕獲組

本文的主要受眾是想要學習正規表示式又不知道從何入手的同學。如果你已經使用過正規表示式,可以快速瀏覽本文,強化自己的記憶即可。

字元匹配

匹配規則

在正規表示式中,分為精確匹配和模糊匹配兩種。顧名思義,精確匹配就是匹配特定的字元或者位置;而非精確匹配就是帶有一定的範圍的匹配。具體示例如下:

const reg1 = /ab/; //精確匹配

const reg2 = /ab+/; //模糊匹配
複製程式碼

不同的匹配適用於不同的場景,大家根據自己的需求進行選擇即可。

字元組

在正規表示式中,我們經常會遇到從N個字元裡面選取任意字元進行匹配的需求。這個時候,我們就需要一個字元組。具體示例如下:

const reg = /[abc]/; // 與上面示例完全相同,匹配a或b或者c
複製程式碼

當匹配的字元多了以後,我們不可能全部都列到字元組裡,因此我們可以使用範圍表示法。具體示例如下:

const reg = /[a-c]/; // 匹配a或b或c
複製程式碼

相同類的字元可以用範圍,如1-9、A-Z或者a-z。在字元組中,-是一個特殊字元,如果需要匹配-,則需要使用\進行轉義。

當然,如果我們是不想匹配N個字元中的任意一個,我們可以用排除字元組的方式來進行匹配。具體示例如下:

const reg = /[^abc]/; // 不匹配a、b、c中任意一個
複製程式碼

排除字元組中也可以使用範圍。

量詞

當我們需要匹配單個字元時,我們可以使用上面示例中的方法。但是,如果我們需要匹配單個字元若干次呢?最簡單的方法就是將匹配的正規表示式寫若干次,但是這樣不僅費時費力,還不方便閱讀。因此,正規表示式中使用了量詞來表示重複匹配N次的情況。

量詞含義如下:

  • {m, },至少出現m次。
  • {m, n},最少出現m次,最多出現n次(最多出現N次的寫法為{0, n},而不是{, n})
  • ,匹配0或者1次
  • +,最少匹配1次,與{1, }等價
  • *,匹配任意次,與{0, }等價

瞭解了上述量詞,下面我們來看下這些量詞在示例中到底是如何應用的:

const reg1 = /a+/; //至少匹配一次a

const reg2 = /a?b*/; //匹配0或者1次a,再匹配任意次的b
複製程式碼

現在問題來了,上面示例中的/a+/這個正規表示式,如果遇到了字串'aaa',那麼得到的匹配結果是什麼呢?這個就涉及到了我們下一節要介紹的內容。

貪婪匹配與非貪婪匹配

貪婪匹配:所有的量詞都會盡可能多的進行匹配,預設值。以/a+/'aaa'為例,匹配的結果是'aaa'。 非貪婪匹配:所有的兩次都會盡可能少的匹配。以/a+?/'aaa'為例,匹配的結果是'a'

因為貪婪匹配是預設值,所以當我們寫正規表示式時,預設就是貪婪匹配。那麼我們應該如何來表示非貪婪匹配呢?具體示例如下:

const reg1 = /a+/; //貪婪匹配

const reg2 = /a+?/; // 非貪婪匹配
複製程式碼

通過上面的示例我們可以看到,我們只需要在兩次後加上一個?,就表示是一個非貪婪匹配。

注:非貪婪匹配只會向後作用,不會向前作用。即/a+?bb/匹配'aabb'是'aabb',而不是'abb';而/aab+?/則是匹配'aab'。(這個與正規表示式匹配和回溯的原理有關,有興趣的可以閱讀我的下一篇關於正規表示式的部落格)

分支邏輯

在一個正規表示式中,我們會遇到做選擇的情況。單個元素進行選擇時,我們可以使用字元組。但是,如果需要多個元素比如ab或者cd進行選擇時,這個時候我們就需要分支邏輯。具體示例程式碼如下:

const reg = /ab|cd/; //表示選擇ab或者cd。為什麼不是b和c呢?這個我們在下一篇部落格——進階篇中將會講述操作符優先順序問題。
複製程式碼

位置匹配

正規表示式除了捕獲字元,還可以捕獲字串中的位置。所謂的位置,指的就是兩個字元之間。比如'ab'這個字串,就有3個位置,分別位於a前面、a後面b前面和b後面。如果我們將位置當成是一個空字串'',其實對於位置的匹配也可以歸納到對字元的匹配中。

匹配位置的方式也有不少,我們來看下:

  • ^,匹配開頭,多行模式下匹配行開頭,即每行開頭都會被匹配。
  • $,匹配結尾,多行模式下匹配行結尾,即每行結尾都會被匹配。
  • \b\w\W之間的位置(\w表示[0-9A-Za-z],而\W就是\w的補集),包括開頭結尾(即也包括\w^之間的位置,和\w$之間的位置)
  • \B,與\b相反,\w\w之間的位置,和\W\W之間的位置,包括開頭結尾(相對的,即包括\W^之間的位置,和\W$之間的位置)
  • (?=p),正向肯定斷言。p是一個子模式,匹配要在p這個模式之前的位置
  • (?!p),正向否定斷言。與(?=p)相反,匹配不要在p這個模式之前的位置

上面說了這麼多,下面我們通過一個示例來一下:

const reg1 = /^ab/; //對於字串'abab'來說,只會匹配到開頭的'ab'
const reg2 = /ab$/; //對於字串'abab'來說,只會匹配到結尾的'ab'
const reg3 = /\b/; //對於字串'a b'來說,會匹配到'a'前面的位置、'a'和' '之間的位置、' '和'b'之間的位置、'b'後面的位置
const reg4 = /\B/; //對於字串'aa bb[/來說,會匹配到'a'和'a'之間的位置、'b'和'b'之間的位置、'['後面的位置
const reg5 = /(?=a)/; //對於字串'bac'來說,會匹配到'a'之前的位置
const reg6 = /(?!a)/; //對於字串'bac'來說,會匹配到'b'之前的位置、'c'之前的位置以及'c'之後的位置
複製程式碼

通過上面的例子,大家應該能夠理解正規表示式在捕獲位置時候所發揮的作用。

ES2018新特性

在ES2018中,增加了反向肯定斷言反向否定斷言。具體格式如下:

  • (?<=p),反向肯定斷言。p是一個子模式,匹配要在p模式之後的位置
  • (?<!p),反向否定斷言。與(?<=p)相反,匹配不要在p模式之後的位置

我們通過一個具體的示例來看下:

const reg1 = /(?<=a)b/ //對於字串'abb'來說,只會匹配到'a'和'b'之間的位置。
const reg2 = /(?<=a)b/ //對於字串'abb'來說,會匹配到'b'和'b'之間的位置。
複製程式碼

括號與捕獲組

在正規表示式中,括號是一個功能非常多的操作符。本章我們將會詳細介紹正規表示式中的括號的各種作用。

提高優先順序

在正規表示式中,運算子操作也有優先順序之分,如下例所示:

const reg1 = /ab|cd/; //匹配'ab'或者'cd'
const reg2 = /a(b|c)d/;//匹配'a'後,匹配一個'b'或者'c',再匹配一個'd'
複製程式碼

關於正規表示式優先順序相關的討論,我們在此就不做展開了,有興趣的同學可以閱讀我的後一篇關於正規表示式高階進階的文章。

捕獲組與非捕獲組

如果我們在正規表示式中,我們需要獲取特定的匹配內容,那麼我們就要用到捕獲組。捕獲組通常使用(p),其中p是一個子模式,表示需要捕獲的內容。具體使用示例如下:

const reg = /a(bc)d/;

let result = 'abcd'.match(reg); // 得到的result[1]就是第一個捕獲組匹配的字元'ab'
複製程式碼

但是,如果我們在一些需要保證優先順序的地方使用了小括號,但是又不想成為捕獲組來干擾匹配,我們應該怎麼辦呢?這個時候我們就需要非捕獲組。我們只需要在括號最開始加上一個?即可。具體使用示例如下:

const reg = /a(?:bc)d/;

let result = 'abcd'.match(reg); // 得到的result沒有捕獲組
複製程式碼

反向引用

當我們在正規表示式中需要使用前面捕獲組匹配的內容時,我們可以使用反向引用。這在匹配一些成對的字元如'"等時非常有效。具體使用方式如下:

const reg = /(a)b\1/; //匹配字元'aba'
複製程式碼

這裡需要注意的有三點:

  1. 如果出現括號巢狀的情況,那麼從左到右以第一個括號(即左開括號)的順序為準。
  2. \10表示的含義為第10個捕獲組,而不是第一個捕獲組加上一個字元0。需要表示後者可以用/(\1)0/。即使是在第三種情況下,轉移符優先順序仍然高於字元順序。
  3. 如果在正規表示式中出現的捕獲組個數小於使用的捕獲組,那麼\字元就會被當成一個轉移符而非反向引用。**注:\2表示對2進行轉義的話,不同的瀏覽器對轉義後的結果是不一樣的。**下圖是Chrome瀏覽器轉義後的結果

image.png

總結

通過閱讀本文,你已經學到了正規表示式的最基礎的語法和使用規則。如果你想提高正規表示式的效率,加快正規表示式的閱讀和理解,可以閱讀正規表示式系列第二篇文章——正規表示式系列之中級進階篇

相關文章