初探正規式

圖片來源: 網路

創造一個正規式

正規式的基本結構通常以/開頭以/結尾,來寫個簡單的正規式

1
var regexp = /xxx/

還可以用 JavaScript內建的建構子來創建一個正規式

1
var regexp = new RegExp('xxx')

tpyeof來檢驗他的型別是什麼

1
2
typeof regexp 
// output: "object"

可以發現他是物件,JavaScript很多東西都是物件型別,包含陣列和函式。

使用正規式來檢驗字串 test()

先宣告一組簡單的字串

1
var str = 'This is a book'

透過 JavaScript正規式所提供的語法 text來檢驗字串使否符合我們的正規式

1
/a/.test(str) // output: true

因為 str 的這個字串內有 a這個字母,因此輸出為 ture

稍微修改一下

1
/z/.test(str) // output: false

由於 str 字串內並沒有 z 字母,所以回傳 false

使用正規式做字串取代 replace()

我們可以透過 replace()方法來做正規式篩選,並取代回傳為 true 的字串內容

1
var str = 'This is a book'

將字串內的 ‘is’ 用 ‘xx’ 取代

1
str.replace('is', 'xx') // output: "Thxx is a book"

有發現什麼地方怪怪的嗎? 為什麼正規式只取代了 this 中的 is,而忽略的 be動詞 is 呢?

取代多個

剛剛的例子中我們其實並沒有使用到正規式,而是用單純的is 字串做篩選而已,一開始我們就提到,正規式是需要包含 /開頭結尾的。

來修來一下程式碼

1
str.replace(/is/, 'xx') // output: "Thxx is a book"

Wait Wait Wait! What? 不對欸不對欸,換成了正規式,輸出結果還是沒有變?

使用正規式取得特定字串 match()

試著取出 HTML 中 h3 標籤間內的文字

1
2
3
4
5
6
7
8
let htmlStr = `    
<li>
<h3>吳先生</h3>
<p>我覺得這個耳機不好用
<span>2017/11/12</span>
</p>
</li>
`

透過 match 方法做篩選

1
htmlStr.match(/<h3>.*?<\/h3>/)

輸出結果

1
["<h3>吳先生</h3>"]

若要取得標籤內文字, 可以透過 () 進行標註

1
htmlStr.match(/<h3>(.*?)<\/h3>/)

結果會將 () 符合條件的文字也推入返回的陣列中

1
["<h3>吳先生</h3>", "吳先生"]

Demo

Regex Flags

其實是這樣的,正規式另外有若干的模式可以做設定,常見的三種參數有 igm

他們分別代表 insensitiveglobalmulti line

補充:除了上述三種模式以外,還有另外三種比較進階的,分別是 stickyunicodesingle line,是在 ES6 以後出現的模式。

global

全局匹配

預設模式只會取代字串中第一個符合的對象,後面的會直接忽略。
現在我們使用 g 模式,如此一來檢驗的目標中所有符合的對象都會順利被取代。

1
str.replace(/is/g, 'xx') // output: "Thxx xx a book"

insensitive

忽略大小寫

一般來說在使用正規式是有區分大小寫的,我們直接看下面的例子。

1
2
3
var str = 'This is a book'

str.replace(/this/, 'That') // output: "This is a book"

結果是沒有任何變化的,因為小寫 this和大寫 This是會被判斷為不同的。

修改一下程式碼

1
2
3
var str = 'This is a book'

str.replace(/this/i, 'That') // output: "That is a book"

加入 i參數,可以忽略大小寫,如此一來輸出結果就如我們所預期的囉!

multi line

多行匹配

這個比較難以敘述,直接用程式碼來說明吧~

1
var str = 'iPhone4\niPhone5\niPhone6\niPhone7\niPhone8'

只要在字串內放入 \n 會產生換行的符號,如果這時候來試試看取代會發生什麼事呢?

1
str.replace(/iPhone/, 'iPad')
1
2
3
4
5
6
// output:
"iPad4
iPhone5
iPhone6
iPhone7
iPhone8"

和剛才的情況一樣只取代了第一個符合的目標。

诶? 這不是一樣用剛剛學到的 g 就可以解決了嗎 ?

來試試看吧

1
str.replace(/iPhone/g, 'iPad')
1
2
3
4
5
6
// output:
"iPad4
iPad5
iPad6
iPad7
iPad8"

確實成功了!

不過!在實際的情況下可能還有意外的事情會發生!

1
str.replace(/^iPhone/g, 'iPad')

上面的向上箭頭 ^ 是一個正規式很常見符號,簡單說明一下它的意義,它會去匹配字串的開頭。我們來看看 str.replace(/^iPhone/g, 'iPad')的輸出結果。

1
2
3
4
5
6
// output:
"iPad4
iPhone5
iPhone6
iPhone7
iPhone8"

現在,又只修改到第一個符合的目標了!

怎麼會醬 ?

/^iPhone/ 的定義為開頭符合字串iPhone的目標,在沒有使用 m 模式的情況下,即使有換行,他們也不會被視為一個新的開頭,可以想像字串就相當於

'iPhone4iPhone5iPhone6iPhone7iPhone8'

這樣的一大串文字,他們的開頭是 iPhone4中的iPhone字眼。

現在我們來修改一下程式碼

1
str.replace(/^iPhone/gm, 'iPad')

輸出結果

1
2
3
4
5
"iPad4
iPad5
iPad6
iPad7
iPad8"

常用情境

資料驗證

有沒有經驗是當你在註冊某些網站的服務時,當你輸入 E-mail 格式不包含 @ 或是一些 .時,網站會有格是錯誤提示,這是怎麼做到的呢?

1
2
3
4
5
6
7
8
9
10
11
12
//please input the test email to see is valid
var strEmail = 'xxx1234@gmail.com';

//Regular expression Testing
var emailRule = /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z]+$/;

//validate ok or not
if(strEmail.search(emailRule)!= -1){
console.log("true");
}else{
console.log("false");
}

上網搜尋一下正規式 表單驗證其實就很容易找到相關的程式碼了。

由於 E-mail 的格式是固定的,為了避免用戶錯誤輸入,將必定驗證不過的E-mail格式傳到資料庫,消耗不必要的資源,通常在前端我們就會透過正規式的技巧來做簡單的篩選。

搜尋功能

相信會看到這篇文章的各位,大部分都有使用文字編輯器。像是筆者使用的文字編輯器是 VS code,我非常常使用到 ctrl + shift + F 的搜尋功能,以往都是使用單純的字串搜尋,但其是現在有非常多的文字編輯器( 或是其他服務 ),都有提供正規式搜尋功能,學會了以後對於資料的篩選精確度會有非常大的提升!

regex101 正規式測試服務

這是一個正規式測式的服務,提供正規式的撰寫測試。
Regex101 Website

設定程式語言

進入網站第一件事情需要先設定程式語言,雖然幾乎所有程式語言都支援正規式,但之中還是有些微差異,有些功能並不是每個程式語言都會支援,但總體來說大概有80%的語法在所有語言都通用。

因為筆者是Web前端開發者,所以這邊我選 JavaScript。

基本功能說明

常用字元

字面值

a b c d 1 2 3 4 等等指定字。

字符類

. [abc] [a-z] \d \w \s

  • . 表示任何字符
  • [abc] 括號表示找到集合裡任意一個字符
  • \d 表示一個數字,等同於[0-9]
  • \w 表示一個單詞字符,等同於[0-9A-Za-z_]
  • \s 表示一個空格,tab,回車或一個換行符
  • [^abc] \D \W \S: 否定字符類,和上述同字母小寫的意義相反

乘法器

{4} {3,16} {1,} ? * +

  • {3,16} 表示找到重複3 到16 次內的字元
  • ? 表示”沒有或出現一次”
  • * 表示”沒有或出現多次”
  • + 表示”一個或出現多次”

分支和組合

(Septem|Octo|Novem|Decem)ber

  • 符號 | 表示”或”
  • 圓括號表示組合,比如在一周中找到一天,使用(Mon|Tues|Wednes|Thurs|Fri|Satur|Sun)day

詞、行和文本邊界

  • ^ 表示行開始
  • $ 表示行結束

反向捕獲組

比如有一段字符,我們需要前面的橫槓去掉,英文句號換成橫槓,尾巴的數字去掉,但是編號文章標題需要保留。

1
2
3
4
5
-1.文章標題1 
-2.文章標題2
-3.文章標題3
-4.文章標題4
-51.文章標題5

將上文宣告成一組換行字串

1
var str = '-1.文章標題1\n-2.文章標題2\n-3.文章標題3\n-4.文章標題4\n-51.文章標題5'

我們預計要將字串替換成 [編號]-[標題]
我們的正規式為 / - (\d+) \. (.+) \d+ / g

RegEx 代表意義
- 固定字串
(\d+) 至少一位數的數字(編號),小括號內的值之後可以用$1輸出
\. 固定字串,由於dot在正規式是有意義的,須以反斜線標記,代表為固定字串.
(.+) 至少一個字的任意字元(標題),小括號內的值之後可以用$2輸出
\d+ 至少一位數的數字
g 全局匹配

而其中$1 $2分別代表正規式中小括號內的字串,他們是由左自右排列的,舉例來說若將 (\d+)的小括號移除,則後面的 (.+)往前遞補,變成 $1的參考值,$2則不存在了。

有時候我們需要括號來作區隔,但是不希望它形成一個群組,意即不希望小括號內的值成為 $的參考值時,我們可以在小括號的前方加上 ?:,就可以把群組取消。如例子中可以將 (\d+)改為(?:\d+),這麼一來現在的 $1參考到的值是 (.+),而 $2則不存在了。

使用replace()方法替換內容。

1
str.replace(/-(\d+)\.(.+)\d+/g, '$1-$2')

1
2
3
4
5
6
// output:
"1-文章標題
2-文章標題
3-文章標題
4-文章標題
51-文章標題"

空白字元

  • 直接打空白鍵 → space
  • \t → TAB
  • \n → 換行
  • \r → 回車符
  • \s* → 所有空白字元

其他

預查

  • 正向預查 (?=)
    有時候我們希望匹配的字串符合某個條件,但不希望該條件也被選取

  • 反向預查 (?!)

    (?=) & (?!) 和上面補充的 (?:)相同,都不會使小括號內值被群組。

參考資料

[偷米騎巴哥]正規式苦手超入門
Jark’s Blog - 正則表達式學習
正規式語法大全
常見正規式範例 - Email 正規式
常見正規式範例 - 驗證整數和小數的正則表達式