寫正則不要再瞎轉義了

在 JavaScript 中,有兩個地方用到了反斜槓轉義序列,一個是在字符串字面量裏,一個是在正則字面量裏。其中字符串字面量裏的反斜槓轉義序列又分爲下面幾種形式:html

1. \ 後面跟着單引號(')、雙引號(")、反斜槓本身(\)、b、f、n、r、t、v 其中的一個 es6

2. \ 後面跟着某個行終止符序列,常見的行終止符序列有三種:回車、換行、回車+換行工具

3. \ 後面跟着 0post

4. \ 後面跟着 1 到 3 個八進制數字firefox

5. \ 後面跟着 x 再跟着兩個 16 進制數字指針

6. \ 後面跟着 u 再跟着 4 個 16 進制數字code

7. \ 後面跟着 u 再跟着 {1個到任意多個的 16 進制數字}regexp

8. \ 後面跟着某個不知足上面全部這些條件的單個字符htm

前 7 種本文不予討論,這第 8 種轉義形式其實就是無效的轉義。舉個例子,好比 \o 就是這樣的轉義,在一些編譯語言裏,這樣的轉義會直接報編譯錯誤,好比在 Java 裏:blog

System.out.println("\o"); // error: illegal escape character 

另外在 JSON 裏也會報錯:

JSON.parse(String.raw`"\o"`) // SyntaxError: Unexpected token o in JSON at position 2

在腳本語言裏一般都不會報錯,它們有兩種選擇,要麼不把 \ 當作轉義字符,而是當作普通的反斜槓字面量,像 Python:

>>> "\o"
'\\o' # len("\o") 爲 2

要麼把 \ 丟棄掉,只留下後面被轉義的那個字符,像 JavaScript:

js> "\o"
"o" // "\o".length 爲 1

哪一種作法好呢?我知道一個保留反斜槓的好處是和正則相關的:在一些沒有正則字面量的語言裏,或者是有些人不會用正則字面量,又或者有時須要從字符串變量動態生成正則的時候,有些人會忘了要雙寫反斜槓,好比:

"www\.taobao\.com" // 在不保留反斜槓的語言好比 JavaScript 裏,這個字符串生成的正則能夠錯誤的匹配到別的域名,好比 "wwwataobao.com"

還有些語言會對這個坑會發出警告:

$ awk -F 'www\.taobao\.com' ''
awk: warning: escape sequence `\.' treated as plain `.'

丟棄反斜槓在其它語言裏有什麼好處我不清楚,但在 JavaScript 裏,我還真知道一個,那就是在一個內聯的 <script> 標籤裏書寫一個包含有 </script> 字樣的字符串時:

<script>
document.wirte("<script src=foo.js><\/script>") // \/ 實際上是無效的轉義,但沒有 \ 的話,這個 </script> 會被 HTML 解析器錯誤的當成結束標籤
</script>

下面纔開始本文的正文,正則字面量中的反斜槓轉義序列比字符串字面量中的更復雜些,分爲下面這麼多種形式:

1. \ 後面跟着 /

2. \ 後面跟着 ^、$、\、.、*、+、?、(、)、[、]、{、}、| 其中的一個

3. \ 後面跟着 c 再跟任意一個字母

4. \ 不存在於 [] 中,後面跟着 b 或者 B

5. \ 存在於於 [] 中,後面跟着 - 或者 b

6. \ 後面跟着 d、D、s、S、w、W 其中的一個

7. \ 後面跟着 f、n、r、t、v 其中的一個

8. \ 後面跟着 0

9. \ 不存在於 [] 中,後面跟着 1 到 3 個十進制數字

10. \ 後面跟着 x 再跟着兩個 16 進制數字

11. \ 後面跟着 u 再跟着 4 個 16 進制數字

12. \ 後面跟着 u 再跟着 {1個到任意多個的 16 進制數字}

13. \ 後面跟着某個不知足上面全部這些條件的單個字符

上面這些轉義形式有些和字符串字面量中的相同,有些則不一樣,甚至有些雖然外表看起來相同,功能卻不一樣。咱們仍是重點關注最後一種狀況,也就是無效轉義的狀況。不少人記不住在正則裏哪些符號應該轉義,哪些不應轉義,好比雙引號 " 在正則裏是不須要轉義的,若是你寫了 /\"/,一般狀況下,JavaScript 的正則引擎會幫你把 \ 去掉:

/^\"$/.test('"') // true

但若是這個正則開啓了 Unicode 模式,則這樣的寫法會致使語法錯誤:

/\"/u // SyntaxError: Invalid escape

那你可能會問,爲何要開啓 Unicode 模式呢?這是由於 Unicode 模式對 BMP 以外的字符支持更友好,好比:

"𠮷野家".match(/./g) // ["�", "�", "野", "家"]
"𠮷野家".match(/./ug) // ["𠮷", "野", "家"]

具體的優勢能夠看這篇文章的總結,總之,若是不考慮兼容性的話,默認加上 /u 老是最佳作法。

還有一個常常被錯誤轉義的字符,那就是連字符 -,連字符只在中括號裏面纔是元字符,在中括號裏面須要轉義,但若是你在中括號外面轉義它的話,一樣在 Unicode 模式下會報錯:

/\-/u // SyntaxError: Invalid escape

另外,從 Firefox 46 和 Chrome 53 開始,在 HTML 表單的 pattern 屬性中填寫的正則開始強制使用 Unicode 模式,好比下面這個 input 的 pattern 屬性就是無效的。打開下面這個 demo,而後打開開發者工具,而後把鼠標指針移動到 input 上,就能看到開發者工具的控制檯出現了報錯信息:

<input pattern="\-" value="foo">

由於這個改動是不向後兼容的,因此一些開發者們發現本身之前運行的好好的代碼忽然報錯了:

轉義了 @ 和 % http://stackoverflow.com/questions/36953775/firefox-error-unable-to-check-input-because-the-pattern-is-not-a-valid-regexp

轉義了 ! https://input.mozilla.org/en-US/dashboard/response/5898357

轉義了 - http://stackoverflow.com/questions/39895209/html-input-pattern-not-working

轉義了 ' https://bugs.chromium.org/p/chromium/issues/detail?id=667713

能夠看見,只要是標點符號,就有人想轉義,由於他們對正則不熟悉,不知道哪些符號是元字符,之前這樣作沒事,但從如今開始,不行了。 

Unicode 模式就像是正則裏的嚴格模式,禁止了不少很差的、容易致使 bug 的寫法,下面再舉一個 /u 禁止了的、和 \ 轉義相關的寫法,那就是 \ 後面跟非 0 十進制數字的狀況:

在非 Unicode 模式下,當 \ 後面是一個非 0 的十進制數字時,若是這個數字對應的捕獲分組恰好存在,則該轉義序列表示反向引用那個分組:

/(f)(.)\2/.test("foo") // true

若是對應的捕獲分組不存在,且數字 < 8 的話,則該序列會被當作八進制轉義序列看待:

/^\2$/.test("\2") // true

若是對應的捕獲分組不存在,且數字 >= 8 的話,反斜槓會被丟棄,只留下數字:

/^\8$/.test("8") // true

也就是說,同樣的轉義寫法可能有三種不一樣的解釋,稍不留神就會致使 bug,代碼也很差讀,所以 Unicode 模式禁用了後兩種狀況,\ 後跟非 0 十進制數字只能表示捕獲分組的反向引用,只要對應的捕獲分組不存在,就報語法錯誤:

/\2/u // SyntaxError:  Invalid escape
/\8/u // SyntaxError:  Invalid escape

總結:本文列舉了幾個在正則的 Unicode 模式下不正確的轉義形式,告誡你們之後在寫正則的時候不能看到標點符號就想轉義,對待知識要一絲不苟。

更高要求:其實正則的 Unicode 模式並無我但願的那麼嚴格,好比正則裏的大多數元字符,實際上在中括號裏並非元字符,是不須要轉義的,但即使 Unicode 模式下也並不會禁止這樣的寫法:

/[\?\+\*]/u // 不會報錯
/[?+*]/u // 應該這麼寫,可讀性比上面的更好
相關文章
相關標籤/搜索