JavaScript中的正則表示式
部分尖括號
<>
內容表示需要手動輸入值
結構
有兩種方法建立一個 RegExp 物件:
-
字面量:
/<regexp choice>/gim
-
建構函式:
new RegExp("<regexp choice>", "gim")
一般推薦使用字面量建立正則表示式;在需要使用變數或為了方便閱讀使用字串拼接時,可使用建構函式形式。
RegExp 能設定三個標識,分別由字母g
、i
、m
表示:
標識 | 含義 |
---|---|
g | 全域性的(匹配多次;不同的方法對g 標識的處理不盡相同) |
i | 大小寫不敏感 |
m | 多行(^ 和$ 能匹配行尾符) |
RegExp 物件的屬性:
屬性 | 用法 |
---|---|
global | 如果標識g 被使用,值為true |
ignoreCase | 如果標識i 被使用,值為true |
lastIndex | 下一次exec 匹配開始的索引。初始值為0 |
multiline | 如果標識m 被使用,值為true |
source | 正則表示式原始碼文字 |
元素
分支
一個正則表示式分支包含一個或多個正則表示式序列。
這些序列被|
字元分割。如果這些序列中任何一項符合匹配條件,那麼這個選擇就被匹配。它嘗試按順序依次匹配這些序列項。故:
"into".match(/in|int/);
會匹配in
。它不會匹配int
,因為in
已經被成功匹配了。
序列
一個正則表示式序列包含一個或多個正則表示式因子。
每個因子可選擇是否跟隨一個量詞,這個量詞決定這個因子被允許匹配的次數。如果沒有指定量詞,則該因子只會被匹配一次。
因子
一個正則表示式因子可以是一個字元、一個由圓括號包圍的組、一個字元類、或者一個轉義序列。
除了控制字元和特殊字元以外,所有的字元都會被按照字面處理:
\ / [ ] ( ) { } ? + * | . ^ $
如果想讓以上字元按字面去匹配,必須要用一個\
字首來進行轉義。
一個未被轉義的.
會匹配除行結束符以外的任何字元。
當指定了g
標識且lastIndex
屬性值為0
或未指定g
標識時,一個未轉義的^
會匹配文字的開始;當指定了m
標識時,它也能匹配行結束符。
一個未轉義的$
將匹配文字的結束。當指定了m
轉義
反斜槓字元\
在正則表示式因子中與其在字串中一樣均表示轉義。
轉義字元含義:
轉義字元 | 含義 |
---|---|
\f | 換頁符 |
\n | 換行符 |
\r | 回車符 |
\t | 製表符(tab) |
\u | 允許指定一個 Unicode 字元來表示一個十六進位制的常量 |
\d | 等同於[0-9] ,它匹配一個數字。\D 則表示與其相反的[^0-9] |
\s | 等同於[\f\n\r\t\u000B\u0020\u00A0\u2028\u2029] 。這是 Unicode 空白符的一個不完全子集。\S 則與其相反 |
\w | 等同於[0-9A-Za-z_] 。\W 則與其相反。\W 本意是希望表示出現在話語中的字元。遺憾的是,它所定義的類實際上對任何真正的語言來說都不起作用 |
\b | 被指定為一個字邊界標識,用於對文字的字邊界進行匹配。遺憾的是,它使用\w 去尋找邊界,所以他對多語言應用來說是完全無用的 |
\1 | 指向分組 1所捕獲到的文字的一個引用,所以它能再次匹配。如正則表示式:d ;\2 是指向分組 2的引用,以此類推 |
如正則表示式:
/(?:^|\s+?)([A-Za-z\u00C0-\u1FFF\u2800-\uFFFD]+)\s+\1(?:$|\s+?)/gi
可以用來搜尋文字中重複的單詞。
分組
在瞭解正則表示式分組前先思考幾個需求:
const htmlStr =
"<div><p><span>content1</span><br/><span>content2</span></p></div><br/>";
-
將所有 p 標籤替換為 span 標籤
-
匹配非 p 或 br 的標籤,全部替換為 div 標籤
捕獲型
一個捕獲型分組是一個被包圍在圓括號中的正則表示式分支。
任何匹配這個分組的字元都會被捕獲。每個捕獲型分組都被指定了一個數字,在正則表示式中第一個捕獲(
的是分組 1,第二個捕獲(
的是分組 2,以此類推。在正則表示式中\<n>
指向第 n 個分組,在正則表示式匹配後RegExp.$<n>
指向第 n 個分組。
由此可以解決需求 1:
htmlStr.replace(/<(\/?)p>/g, "<$1span>"); // <div><span><span>content1</span><br/><span>content2</span></span></div><br/>
但如果我匹配出
p
而不想匹配出尖括號<>
呢?
非捕獲型
非捕獲型分組有一個(?:
字首(捕獲型僅為(
字首)。
非捕獲型只做簡單匹配,並不會捕獲所匹配的文字,這會帶來微弱的效能優勢,且不會干擾捕獲型分組的編號,即無法通過引用符\<n>
或$<n>
來對其引用(非捕獲性)。
向前正向匹配(前瞻正向斷言)
向前正向匹配分組有一個(?=
字首。
它類似於非捕獲型分組,但在這個分組匹配成功後,文字會回到它開始的地方,實際上並不匹配任何東西。它只匹配一個位置,如同^
匹配開頭,$
匹配結尾。
如:x(?=y)
表示匹配x
,僅在後面是y
的情況
向前負向匹配(前瞻負向斷言)
向前負向匹配分組有一個(?!
字首。
它類似於向前正向匹配分組,但只有當它匹配失敗後,它才會繼續向前匹配。
如:x(?!y)
表示匹配x
,僅在後面不是y
的情況
由此可以解決需求 2:
htmlStr.replace(/<(\/?)(?!p|\/p|br\/?).*?>/g, "<$1div>"); // <div><p><div>content1</div><br/><div>content2</div></p></div><br/>
值得注意的是:對於匹配
p
或者/p
標籤的規則(\/?)(?!p|\/p)
中,|\/p
規則是不能省略的,否則:"<p></p>".match(/<(\/?)(?!p).*?>/g); // ["</p>"]
這裡正則向前匹配,第一個
<p>
標籤沒什麼好說的;在第二個</p>
標籤匹配到/
時,由於後面是個向前負向匹配,需要對/
進行前瞻斷言,但是後面是字元p
,所以不匹配/
。此時文字會回到<
重新斷言(此時(\/?)
匹配為空),後面是字元/p
,故可以被該規則匹配。
向後正向匹配(後瞻正向斷言)
向後正向匹配分組有一個(?<=
字首(es2018 才支援向後匹配)。
它類似於向前正向匹配,不過它是在相反的方向上進行條件判斷。
如:(?<=y)x
表示匹配x
,僅在前面是y
的情況
向後負向匹配(後瞻負向斷言)
向後負向匹配分組有一個(?<!
字首。
如:(?<!y)x
表示匹配x
,僅在前面不是y
的情況
現在我們可以解決前面的一個問題了。如何不匹配出尖括號
<>
:htmlStr.replace(/(?<=<)(\/?)p(?<!>)/g, "$1span"); // <div><span><span>content1</span><br/><span>content2</span></span></div><br/>
這和上面的正則是一個效果。
字符集(類)
正則表示式字符集是一種指定一組字元的便利方式。
如:如果想匹配一個母音字母,我們可以寫做
(?:a|e|i|o|u)
,但它可以被更方便地寫成一個類[aeiou]
。
另一個方便之處是類的求反。如果[
後的第一個字元是^
,那麼這個類會排除這些特殊字元。
如:
[^!-\/:-@\[-`{-~]
會匹配任何一個非 ASCII 特殊字元的字元。
字元轉義
字元類內部的轉義規則和正則表示式因子的相比稍有不同。
[\b]
表示退格符(backspace)。下面是在字元類中需要被轉義的特殊字元:
- \ / [ ] ^
量詞
正則表示式因子可以用一個正則表示式量詞字尾來決定這個因子應該被匹配的次數。
包圍在一對花括號中的數字表示這個因子應該被匹配的次數。故,/www/
和/w{3}/
匹配的是一致的,{3,6}
會匹配 3~6 次,{3,}
會匹配 3 次或更多。
?
等同於{0,1}
;*
等同於{0,}
;+
等同於{1,}
如果只有一個量詞,表示趨向於進行貪婪匹配,即匹配儘可能多的副本直至上限;
如果這個量詞附加一個字尾?
,則表示趨向於進行非貪婪匹配,即只匹配必要的副本。
在上述需求 2的解決方案
htmlStr.replace(/<(\/?)(?!p|\/p|br\/?).*?>/g, "<$1div>");
的.*?
表示只匹配一個尖括號中的字元,否則該正則表示式將貪婪匹配整個字串:htmlStr.replace(/<(\/?)(?!p|\/p|br\/?).*>/g, "<$1div>"); // <div>