三十分鐘包會——正則表達式

正則表達式,對大家來說既熟悉又陌生。熟悉是因爲工作中有很多場景能用到,比如手機號、郵箱、密碼等規則校驗。

陌生則是因爲正則表達式看上去就是一堆亂碼,且一眼看上去很難看懂匹配規則。有時候在網上去找一個特定規則的正則表達式,搜出來的結果各不相同,執行效果更是不盡人意,想自己去修改,感覺也無從下手。

今天就花費 30 分鐘時間,帶領大家從另一個角度去剖析匹配的目的,理解匹配的思路,一步一步抽絲剝繭去學會怎麼寫正則表達式(讀正則表達式遠比寫表達式要困難)。

正則要乾的事情,可以總結爲以下靈魂三問:

Q1、匹配啥?
Q2、匹配不是啥?
Q3、匹配多少次?

Q1、匹配啥?

這個比較好理解,比如想匹配字符 a,那就直接寫/a/,只要字符串某個位置是 a 就可以匹配上:

1/a/.test("javascript") 

匹配以 a 開頭的字符串,就加上元字符^(開始位置標識),/^a/

1/^a/.test("javascript") 
2/^a/.test("abc") 

匹配以 a 結尾的字符串,就加上元字符$(結束位置標識),/a$/

1/a$/.test("javascript") 
2/a$/.test("cba") 

匹配字符 a 或 b,可以把對應的字符放入中括號裏/[ab]/,只要字符串包含 a 或者 b 就可以匹配上:

1/[ab]/.test("byte") 

匹配字符串 abc 或 xyz,/abc|xyz/

1/abc|xyz/.test("aabbxyz") 

① 匹配某表達式前面的(前瞻)

exp1(?=exp2):匹配 exp2 前面的 exp1,匹配結果不包含 exp2

比如要匹配字符串中 script 前面的部分 java, /java(?=script)/

1/java(?=script)/.test("javascript,javaee,typescript") 
2
3
4/java(?=script)/.exec("javascript,javaee,typescript")
5
6["java", index: 0, input: "javascript,javaee,typescript", groups: undefined]
7

② 匹配某表達式後面的(後顧)

(?<=exp2)exp1:匹配 exp2 後面的 exp1,匹配結果不包含 exp2

比如要匹配字符串中 java 後面的部分 ee, /java(?>=ee)/

1/(?<=java)ee/.test("javascript,javaee,typescript") 
2
3
4/(?<=java)ee/.exec("javascript,javaee,typescript") 
5
6["ee", index: 15, input: "javascript,javaee,typescript", groups: undefined]
7

Q2、匹配不是啥?

匹配不是啥,意思就是取反,只要不是這些的都可以匹配,比如不想匹配字符 a,正則寫法爲/[^a]/,是在中括號裏面加上元字符^,這樣只要字符串滿足有不是這個集合裏面的字符,都可以被匹配上:

1/[^a]/.test("aaa") 
2/[^a]/.test("abc") 

匹配不是以 a 開頭的,跟之前匹配啥類似,加上元字符/^[^a]/

1/^[^a]/.test("javascript") 
2/^[^a]/.test("abc") 

匹配不是以 a 結束的,也跟之前匹配啥類似,加上元字符/[^a]$/

1/[^a]$/.test("javascript") 
2/[^a]$/.test("cba") 

不匹配字符 a、b、c,可以把對應的字符都放入中括號裏/[^abc]/

1/[^abc]$/.test("abccba") 

① 匹配後面不是某表達式的(負前瞻)

exp1(?!exp2):匹配後面不是 exp2 的 exp1,匹配結果不包含 exp2

比如要匹配字符串中後面不是 script 的 java, /java(?!script)/

1/java(?!script)/.test("javascript,javaee,typescript") 
2
3
4/java(?!script)/.exec("javascript,javaee,typescript")
5
6["java", index: 11, input: "javascript,javaee,typescript", groups: undefined]
7

② 匹配前面不是某表達式的(負後顧)

(?<!exp2)exp1:匹配前面不是 exp2 的 exp1,匹配結果不包含 exp2

比如要匹配字符串中前面不是 java 的 script,/(?<!java)script/

1/(?<!java)script/.test("javascript,javaee,typescript") 
2
3
4/(?<!java)script/.exec("javascript,javaee,typescript")
5
6["script", index: 22, input: "javascript,javaee,typescript", groups: undefined]
7

③ 不匹配包含 abc 的字符串

這是一個比較特殊的匹配行爲,如果只是寫成/[^abc]/的話,這隻意味着字符串不能全是由 a、b、c 這三個組成的,跟需求不匹配。

那我們要從另外一個角度去分析,字符串的任意一個位置開始都不能連續出現 abc,我們可以利用負前瞻來實現:

  1. 位置後面都不能是 abc,使用負前瞻匹配空位置:/(?!abc)/
  2. 從開始到結束每個位置都要覆蓋到,添加開始結束標記:/^(?!abc)/$
  3. 這個位置後面可以是其他的字符,用\w來表示:/^(?!abc)\w$/
  4. 滿足上面情況後的位置,可以連續出現多個,用+來表示數量:/^((?!abc)\w)+$/
1/^((?!abc)\w)+$/.test("cbacbac") 
2/^((?!abc)\w)+$/.test("cbacbabc") 

Q3、匹配多少次?

匹配一次可以什麼都不用定義,比如匹配一個數字/\d/,如果要匹配連續三個數字最簡單的方式就是連續寫三次:/\d\d\d/,這樣寫本身是沒有問題的,能正確匹配。

但是如果次數太多或者次數不確定,這麼寫肯定不行,所以可以加上長度規則:

*:匹配任意次

+:最低匹配 1 次

?:匹配 1 次或者 0 次

{m}:匹配 m 次

{m,}:最低匹配 m 次

{m,n}:最低匹配 m 次,最多匹配 n 次,m 需要小於等於 n

正則默認是貪婪匹配,就是符合條件的會一直匹配,如果想阻止貪婪匹配,可以在長度規則後面加一個?,比如:

1/\d{2,}/.exec("1234567890")
2
3["1234567890", index: 0, input: "1234567890", groups: undefined]
4
5
6/\d{2,}?/.exec("1234567890")
7
8["12", index: 0, input: "1234567890", groups: undefined]

① 使用分組

如果想匹配多次某個單詞如 regregregregregreg 時候怎麼辦,我們看到 reg 連續出現了 6 次,如果傻傻的把 6 個 reg 寫在了正則表達式中肯定不合適,我們就可以利用分組來實現,我們把 reg 放在括號裏面,然後讓這個分組重複 6 次,/(reg){6}/

1/(reg){6}/.test("regregregregregreg") 

不過這樣利用分組是有個前提,就是知道要匹配的字符串就是 reg,然後重複這個分組。如果想匹配類似 8899 或者 5522 這種重疊類型的字符怎麼辦呢?那我們可以把重疊的第一個放入分組,再使用這個分組匹配下一個:

1/(\d)\1(\d)\2/.exec("2345566789")
2
3["5566", "5", "6", index: 3, input: "2345566789", groups: undefined]

② 分組捕獲

默認的分組是可以被捕獲的,上面的\1\2是在正則表達式內部捕獲的分組。如果想在外部去捕獲分組匹配的數據可以使用RegExp.$1-$9來獲取。只要正則匹配了就會有。可以使用testexec或者 str 的replace方法來獲取$1-$9

使用 test:

1/([a-z]{2})(\d{2})/.test("xyz123")
2RegExp.$1 
3RegExp.$2 

使用 replace:

1"xyz123".replace(/([a-z]{2})(\d{2})/,'$2$1')
2

③ 分組不捕獲

如果不想捕獲分組,只需要在分組內加上?:就可以了

1/([a-z]{2})(?:\d{2})/.test("xyz123")
2RegExp.$1 
3RegExp.$2 

本文不去贅述元字符的含義以及組合使用方法,這些是需要記死背硬的東西。而是教給大家一種如何切入正則表達式的思維,怎麼去一步步分析要匹配的需求,把長的、複雜的需求拆成短的、簡單的,正向不行的就逆向去分析,一點一點去組合,然後迴歸靈魂三問:匹配啥?匹配不是啥?匹配多少次? ,去完成符合要求的正則表達式。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://juejin.cn/post/6939854031787393031?utm_source=gold_browser_extension