我從「使用正則表達式獲取URL的查詢參數」學到了什麼

在看博客的時候,不經意間看到了一道這樣的面試題:如何使用正則表達式從URL上獲取查詢參數?html

本身思考了一下,而後嘗試用正則表達式來實現這個功能,結果發現本身對正則表達式仍是不夠熟悉;因而就順着這道題目,一步一步的去深挖這裏面所涉及到的知識點。es6

之後會不按期更新「我從xxx學到了什麼」系列,也是給本身學到的知識進行一個階段性的總結吧!面試

經過這邊文章,你能夠學到如下知識點(如下全部代碼都是基於JavaScript語言)正則表達式

  1. 正則表達式的修飾符
  2. 正則表達式的貪婪模式與懶惰模式
  3. 正則表達式的分組、分組編號、分組引用
  4. 正則表達式的先行斷言/先行否認斷言,後行斷言/後行否認斷言

前提條件

URL上的查詢參數能夠直接從window.location.search獲取,獲得的字符串就是如下形式數組

?key1=value1&key2=value2markdown

因此題目就能夠轉換成:使用正則表達式從以上字符串獲取全部的鍵值對。函數

如下全部的匹配字符串都是 '?name=jim&age=20&hobby=basketball'oop

預期獲得的結果學習

1、name=jim
2、age=20
3、hobby=basketball
複製代碼

初版

思路:url查詢參數部分的字符串格式是固定的:以?開頭,後面接key=val,每組鍵值對以&鏈接url

話很少說,思路有了,就直接上代碼

/[?&](.*)=(.*)/g
複製代碼

輸出的結果

'?name=jim&age=20&hobby=basketball'
複製代碼

竟然整個字符串都被匹配上了,問題究竟出在哪裏?

想法是匹配以?&開頭的子串,後面跟着key=val,因此寫出了 (.*)=(.*),這個按理應該能匹配出name=jim這樣的字符串。爲何在第一個(.*)就把後面的整個字符串都匹配上了呢?想要的結果是第一個(.*)匹配到 = 就結束了,也就是key,而後第二個(.*)繼續匹配val。之因此給他們都加上()就是想在=前中止匹配,但實際上卻沒有按照預期獲得正確結果。

爲何會出現這種狀況呢?因而去百度瞭如下,結果發現,正則表達式默認元字符,量詞是採用貪婪模式

什麼是貪婪模式?

貪婪模式會盡量多的匹配知足條件的字符。

以下,都是會匹配最大長度字符串

.*
.+
.{1,}
.{0,} 
複製代碼

正則表達式元字符,量詞默認首先最大匹配字符串,這些量詞有:+,*,?,{m,n} 。一開始匹配,就直接匹配到最長字符串。

以下 <h3>abd</h3><h3>bcd</h3> 經過正則表達式:/<h3>.*</h3>/g 進行匹配,獲得的結果爲:<h3>abd</h3><h3>bcd</h3> 也就是整個字符串都被匹配上了。

知道了正則表達式默認是貪婪模式,那麼/[?&].*=.*/g爲何會匹配整個字符串的緣由找到了。

問題就出如今.*,因爲它會盡量多的匹配符合條件的字符串,因此它會把整個字符串都匹配上。

這時就有一個問題:有沒有辦法讓它最小匹配呢?

答案是確定有的,可使用懶惰模式

什麼是懶惰模式

和貪婪模式相反,懶惰模式是進行最小匹配。

如何轉換成懶惰模式

很簡單,在表示重複字符元字符,後面加多一個?字符便可。

上面的正則表達式若是想最小長度匹配,則能夠這樣寫:/<h3>.*?</h3>/g 那麼匹配出的結果爲:

<h3>abd</h3>   
<h3>bcd</h3>
複製代碼

分組

在一個正則表達式中,若是給子表達式加上(),就表明這部分是一個分組,整個表達式是第一個分組。

舉個栗子

/[?&](.*)=(.*)/g
複製代碼

其中(.*)就是一個分組。

分組有什麼用?

通常分組有兩個做用:一、在結果中獲取特定的分組匹配結果;二、在表達式中進行引用

  1. 在結果中獲取特定的分組匹配結果

    執行regex.exce(str)時,獲得的結果是一個數組;以下

    假如要匹配一個年份的年月日,一個簡單的正則能夠是這樣的

    /(\d{4})-(\d{2})-(\d{2})/g
    複製代碼

    匹配字符串1993-01-01獲得的結果以下

    [
        "1993-01-01",
        "1993",
        "01",
        "01"
    ]
    複製代碼

    第一個是整個表達式的匹配結果,第二個是第一個分組的匹配結果,依次類推。

    那麼問題來了,我怎麼知道某個分組是第幾個?

    分組的編號規則

    分組能夠經過從左到右計算其開括號來編號。整個表達式始終是第一個分組。

    以下表達式 (A)(B(C)) 有四個分組 0 (A)(B(C)) 1 (A) 2 (B(C)) 3 (C) 在執行exec函數時,返回的結果也是按照上面的分組返回結果。 第一個返回結果是0號分組匹配到的字符串;第二個返回結果是1號分組匹配到的字符串;依次類推

  2. 在表達式中進行引用

    在一些場景中,須要匹配和前面某個子表達式匹配結果同樣的結果,好比說,須要匹配重複的單詞,那麼就可使用分組引用。

    一樣舉個栗子,匹配重複的單詞

    abc abc

    能夠經過如下表達式進行匹配

    /\b(\w+)\b\s+\1\b/
    複製代碼

    \1:引用第一個分組匹配到的結果,上面的栗子就是引用(\w+)獲得結果,也就是abc

    須要注意的是,引用分組進行匹配,僅僅是引用,它並非真正的分組,因此在結果的數組中是不會有這個引用匹配到的結果。

知識點補充

JavaScript正則表達式的修飾符
  1. g:全局匹配
  2. i:忽略大小寫
  3. m:多行匹配
  4. s:使用dotAll模式匹配
  5. y:「粘連」匹配

以上修飾符,若是有不理解的,能夠去ES入門教程-正則表達式學習下,這裏就再也不贅述了。

JavaScript正則表達式的元字符

不熟悉的能夠去菜鳥教程學習下,這裏就再也不贅述了。

第二版

通過初版的慘痛失敗,意識到了要使用懶惰模式進行匹配,因此改進如下獲得第二版表達式

[?&]?(.*)?=(.*)?
複製代碼

輸出結果

1、?name=
2、jim&age=
320&hobby=
複製代碼

一共匹配到了三個,但結果仍是不是我預期的,

問題在哪裏呢?

[?&]?:匹配上的字符串以?或&或都沒有開頭

.*?:匹配任意字符串,匹配數可使0或者1,也就是說能夠是什麼都不匹配

.*?=.*?:第一個.*?會匹配到=前的字符串,而第二個.*能夠什麼都不匹配,由於它是懶惰模式

並且從預期的結果來看,在匹配結果中,並不須要?&。僅僅是想匹配?或&後面的ke=val。

這時候,後向斷言就能夠發揮做用了。

什麼是反向斷言

這個名字讀起來就很彆扭,不用去理解它的具體含義,權當是一個名字就行。

語法

(?<=exp)x
複製代碼

斷言部分在()內,它的做用是匹配知足x的字符串時,校驗x前面的字符串是否知足exp,若是知足,則匹配,不然不匹配。

舉個具體的栗子

只匹配在#後面的數字

const reg = /(?<=#)\d+/g
const str = '#90abc'

str.match(reg)
// [90]
複製代碼

反行否認斷言

語法

(?<!=exp)x
複製代碼

它的做用是匹配知足x的字符串時,校驗x前面的字符串是否知足exp,若是不知足,則匹配,不然不匹配。

舉個具體栗子

不匹配#後面的數字

const reg = /(?<!#)\d+/g
const str = '#90abc$100edf%102ooo'

str.match(reg)
// [0, 100, 102]
複製代碼

正向斷言

語法

x(?=exp)
複製代碼

它的做用是匹配知足x的字符串時,校驗x後面的字符串是否知足exp,若是知足,則匹配,不然不匹配。

舉個具體栗子

只匹配百分號前的數字

const reg = /d+(?=%)/  
const str = '90%'

str.match(reg)
// [90]
複製代碼

正向否認斷言

語法

x(?!exp)
複製代碼

它的做用是匹配知足x的字符串時,校驗x後面的字符串是否知足exp,若是不知足,則匹配,不然匹配。

舉個具體栗子

只匹配不在百分號前的數字

const reg = /d+(?!%)/g  
const str = '100$'

str.match(reg)
// [100]
複製代碼

終版

又多了一件厲害的裝備,如今是時候祭出最終版的表達式了

/(?<=[?&])[^&]*/g
複製代碼

輸出結果

1、name=jim
2、age=20
3、hobby=basketball
複製代碼

(?<=[?&]):後向斷言,匹配後面的表達式以後,再次進行驗證,若是都知足,則匹配

[^&]*:匹配不是&的任意字符

總結

  1. 默認匹配模式是貪婪模式,若是要轉換成懶惰模式,則在元字符或量詞後加 ?
  2. 分組編號規則,設置分組後的到的匹配結果,以及分組的兩個使用場景
  3. 斷言能夠額外新增匹配條件,斷言內匹配上的表達不會出如今最終匹配結果中
相關文章
相關標籤/搜索