[JS] RegExp 參數的正確姿勢

雖然說寫了很多 JavaScript,但是還真的是頭一次遇到 global 的問題。其實也不是什麼問題,只是對於這個物件的不熟悉,所以覺得他是一個 Bug,後來才發現他是一個 Feature。


RegExp

在開始之前,我們可以先參考一下 MDN 的文件,

https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Regular_Expressions

然後可以看一下關於 Advanced Searching With Flags 的區段,在正規表達式的規則中,MDN 提出 JavaScript 有四種 Flags 可以使用,

  1. g 全域搜尋
  2. i 不區分大小寫
  3. m 多行搜尋
  4. y 從指定的位置開始搜尋

然後其實偷看一下 RegExp 這個元件,他還會有 unicodedotAll 這兩個 Flag 可以使用,分別是,

  1. u 使用 Unicode 的功能搜尋
  2. s 代表 dotAll,是一個新的提案,代表任意單一字元(包含換行)

先說踩到的雷

應該說是一個 Feature!由於對於 RegExp 的不熟悉,加上又太習慣用 g 做全域搜尋,結果就造成了這次的慘案。舉一個類似的例子來看,

regexp_test_01

WTF!!!

對,我第一次的反應也是這樣。後來詳細查詢了 g 的參數之後,發現 RegExp 元件有一個東西很有趣,叫做 lastIndex,而 g 這個參數完整的用意是,

對於所有的文字進行搜尋,找到比對結果後,會修改 lastIndex 到該結果的位置,下一次再次執行比對方法時,會從這個位置繼續開始往下找。

簡單的用圖片來解釋,

regexp_test_02

那中間的 false 到底是怎麼回事?依兆剛剛的說明,我們把整個步驟拆解出來看,他就會像是這樣,

const a = new RegExp('123', 'g');
const b = '123 456 789 123 456 789';

// 做第一次比對
a.test(b);

// 比對成功後,這個時候 lastIndex 會改變
console.log(a.lastIndex);

// lastIndex 會印出 3

// 做第二次比對,由於 lastIndex === 3,所以會從第 3 個位置開始往後搜尋
a.test(b);

// 比對成功後,這個時候 lastIndex 會改變
console.log(a.lastIndex);

// lastIndex 會印出 15

// 做第三次比對,由於 lastIndex === 15,所以會從第 15 個位置開始往後搜尋
a.test(b);

// 但是這次會比對失敗,因為第 15 個位置之後,找不到 123 了
// 這個時候 lastIndex 會自動歸零
console.log(a.lastIndex);

// lastIndex 會印出 0

這就是為何會出現一下子是 true 然後一下子是 false 的原因。之所以會覺得這個狀況有疑慮,是因為我製作一個很外層的變數,然後每次比對網址都呼叫該正規表達式物件來幫我做,但是,我又 沒有把 lastIndex 歸零,所以變成比對的情況會依照 呼叫的次數 產生預期外的結果。

對不起我錯了。

g 的用意是可以讓你找出全部的字串,而當你呼叫了 exec 或是 test 的時候,他會改變 lastIndex,好讓你可以再做一次 exectest。所以,當你需要 重頭開始 的時候,請先把 lastIndex 歸零

https://stackoverflow.com/questions/11477415/why-does-javascripts-regex-exec-not-always-return-the-same-value

不歸零會怎樣?

regexp_test_03

a.test(b);a.test(c); 距離很遠,或是根本在不同地方的時候(嘿嘿,請不要惡搞同事好嗎

'y' 參數

他跟 lastIndex 也是有關的,所以既然提到了就順便說一下。

但是!

這個參數有一個隱含的操作,就是在你的正規表達式當中,會幫你帶一個 ^ 來做比對。換句話說,當你使用了這個參數,那麼每次比對都會強迫使用開頭比對來進行。舉例來說,

regexp_test_04

媽媽有說收工前不能罵髒話。

我剛剛提及了 每次比對都會強迫使用開頭比對來進行 。所以你第二次的比對會出現 false 是正確的,因為這個 test 會修改 lastIndex 的數值,當我做了第一次的 test 之後,那個 lastIndex 會變成 3。

接著,第二次的 test 會從 lastIndex = 3 的位置開始,然而 y 又是 強迫開頭比對 ,所以,當從第 3 個位置開始,比對 /^123/ 的結果,就會是 false

regexp_test_05

那麼,你現在應該可以理解上圖,第一次的 test 為何為 false 了吧。那麼,你猜猜看上面的 lastIndex 最後會變成多少?

dotAll, 's' 參數

目前不確定瀏覽器支援程度,不過我在 Chrome DevTools 可以操作。他的意思是就是取代任何 單一 字節的文字,可以參考一下我上面的連結

小結

沒事不要學人家寫什麼 JavaScript

ES6 正規表達式擴展參考

Hina Chen
偏執與強迫症的患者,算不上是無可救藥,只是我已經遇上我的良醫了。
Taipei