本文適合有 JavaScript 基礎 && 面向搜索引擎書寫正則的人群。
正則表達式是用於匹配字符串中字符組合的模式。正則表達式的模式規則是由一個字符序列組成的。包括所有字母和數字在內,大多數的字符都是直接按照直接量描述待匹配的字符。除此之外,正則表達式還有其他特殊語義的字符,這些字符不按照特殊含義進行匹配。
創建正則表達式
JavaScript 中的正則表達式用 RegExp 對象表示,有兩種創建方式。
1. 直接量語法創建
正則表達式直接量定義為包含在一對斜杠(/
)之間的字符。
2. 構造函創建
可以通過 RegExp()
構造函數可以實現動態創建正則表達式。RegExp
的第二個參數是可選的。
new RegExp(pattern [, flags])
RegExp(pattern [, flags])
其中 pattern
可以是字符串或者正則字面量。當 pattern
是字符串時,需要常規的字符轉義規則,必須將 \
替換成 \\
,比如 /\w+/
等價於 new RegExp("\\w+")
。
直接量字符
正則表達式中所有字母和數字都是按照字面含義進行匹配的,其他非字母的字符需要通過反斜杠(\
)作為前綴進行轉移,如 \n
匹配換行符。這些字符為直接量字符(literal characters)。這些字符都是精確匹配,每一個字符都只能匹配一個字符。
在正則表達式中,有一些標點符號具有特殊含義,他們是:^ $ . * + ? = ! : | \ / ( ) [ ] { }
如果需要在正則表達式中與這些直接量進行匹配,必須使用前綴 \
。
如果不記得哪些標點需要反斜杠轉義,可以在每個標點符號前都加上反斜杠。
字符類
如果不想匹配某一個特定的字符而是想匹配某一類字符,則需要使用字符類。
通過將直接量字符放入方括號內,可以組成字符類(character class)。一個字符類可以匹配它所包含任意 一個 字符。如 [abc]
可以匹配 a,b,c 中任意一個字符。
使用 ^
作為方括號中第一個字符來定義否定字符集,它匹配所有不包含在方框括號內的字符。[^]
可以匹配任意字符。
字符類可以使用連字符來表示字符范圍。比如匹配小寫字母[a-z]
,匹配任何字母和數字可以用[a-zA-Z0-9]
。
一些常用的字符類,在 JavaScript 中有特殊的轉義字符來表達它們。
字符 | 匹配 |
---|---|
[...] |
方括號內任意字符 |
[^...] |
不在方括號內任意字符 |
. |
除了換行符和其他 Unicode 行終止符之外的任意字符 |
\w |
等價於 [a-zA-Z0-9_] |
\W |
等價於 [^a-zA-Z0-9_] |
\s |
任何 Unicode 空白符 |
\S |
任何非 Unicode 空白符的字符 |
\d |
等價於 [0-9] |
\D |
等價於 [^0-9] |
[\b] |
退格直接量,與退格鍵 \u0008 匹配,注意不同於 \b |
方括號內也可出現轉義字符,如 [\d\s]
表示匹配任意空白符或數字。
重復
當一個模式需要被多次匹配的時候,正則表達式提供了表示重復的正則語法。
字符 | 含義 |
---|---|
{n,m} |
匹配前一項至少 n 次,但不能超過 m 次 |
{n,} |
匹配前一項至少 n 次 |
{n} |
匹配前一項 n 次 |
? |
匹配前一項 0 次或 1 次,等價於 {0,1} |
+ |
匹配前一項 1 次或多次,等價於 {1,} |
* |
匹配前一項 0 次或多次,等價於 {0,} |
貪婪和非貪婪的重復
上面所有的重復都是“貪婪的”匹配,也就是匹配盡可能多的字符。如 /a+/
匹配 'aaaa'
時,它會匹配 'aaaa'
。
如果想要盡可能少的匹配,只需要在重復的標記后加一個問號(?
)即可。如 /a+?/
匹配 'aaaa'
時,它會匹配 'a'
。
注意:正則表達式的模式匹配總會尋找字符串中第一個可能匹配的位置,這意味這 /a+?b/
匹配 'aaab'
時,匹配到的是 'aaab'
而不是 'ab'
。
選擇、分組和引用
選擇
字符 |
用於分隔供選擇的模式,匹配時會嘗試從左到右匹配每一個分組,直到發現匹配項。如 /ab|bc|cd/
可以匹配字符串'ab'
、'bc'
和 'cd'
。
分組
圓括號可以把單獨的項組合成子表達式,以便可以像一個獨立的單元用 |
、*
、+
或者 ?
對單元內的項進行處理。
引用
帶圓括號的表達式的另一個用途是允許在同一個正則表達式的后面引用前面的子表達式。通過\
后面加數字實現。\n
表示第 n 個帶圓括號的子表達式。表示引用前一個表達式所匹配的文本。因為子表達式可以嵌套,所以根據子表達式左括號的位置進行計數。
例,能匹配 1999-01-01 或 1999/01/01 的正則:/\d{4}([-\/])\d{2}\1\d{2}/
具名引用
使用 (?<name>...)
的語法來為分組命名,並通過 \k<name>
在后面的正則表達式中引用。如上面的正則可以改寫為:/\d{4}(?<separator>[-\/])\d{2}\k<separator>\d{2}/
忽略引用
如果只想用圓括號來表示子表達式,而不希望生成引用,可以使用 (?:)
來進行分組。例,/(?:a)(?:b)(c)/
中 \1
將表示 (c)
所匹配的文本。
指定匹配位置(錨元素)
有一些正則表達式的元素不用來匹配實際的字符,而是匹配指定的位置。我們稱這些元素為正則表達式的錨。
正則表達式中的錨字符包括:
^
用來匹配字符串的開始,多行檢索時匹配一行的開頭。$
用來匹配字符串的結束,多行檢索時匹配一行的結尾。\b
用來匹配單詞的邊界,就是\w
和\W
之間的位置,或者\w
和字符串的開頭或結尾之間的位置。\B
匹配非單詞邊界的位置。
例: /\bJava\b/
可以匹配 Java
卻不匹配 JavaScript
。
任意正則表達式都可以作為錨點條件。
先行斷言
(?=pattern)
它表示一個位置,該位置之后的字符能匹配 pattern
。如 /\d+(?=%)/
匹配字符串 '100%'
中的 '100'
但是不匹配 '100。'
負向先行斷言
(?!pattern)
它表示一個位置,該位置之后的字符能不匹配 pattern
。
后行斷言
(?<=pattern)
它表示一個位置,該位置之前的字符能匹配 pattern
。例,/(?<=\$)\d+/
匹配 '$100'
但是不匹配 '¥100'
。
負向后行斷言
(?<!pattern)
它表示一個位置,該位置之前的字符能不匹配 pattern
。
修飾符
在正則表達式的第二條斜線之后,可以指定一個或多個修飾符,/pattern/g
。
常用修飾符:
i
執行不區分大小寫的匹配。g
全局匹配。m
多行匹配模式。y
“粘連”(sticky)修飾符。y修飾符的作用與g修飾符類似,也是全局匹配,后一次匹配都從上一次匹配成功的下一個位置開始。不同之處在於,g修飾符只要剩余位置中存在匹配就可,而y修飾符確保匹配必須從剩余的第一個位置開始,這也就是“粘連”的涵義。s
表示點(.)可以表示任意字符,不設置的話,四個字節的 UTF-16 字符和行終止符不能用 . 表示。u
開啟 “Unicode 模式”,用來正確處理大於\uFFFF
的 Unicode 字符。也就是說,會正確處理四個字節的 UTF-16 編碼。
通過 RegExp.prototype.flags
可以獲得正則修飾符的字符串。/pattern/ig.flags
返回 "gi"
字符串的正則方法
String.prototype.search(regexp|substr)
返回第一個和參數匹配的子串的起始位置。沒有匹配子串返回 -1
。
如果參數不是正則表達式,將會通過 RegExp
構造函數轉換成正則表達式。它會忽略正則的修飾符 g
。
String.prototype.replace(regexp|substr, newSubStr|function)
第一個參數同search
,查找指定子串。如果第二個表達式是字符串,將把第一個參數匹配的子串替換為 newSubStr
。如果在替換字符串中出現了 $
加數字,replace
將用與指定的子表達式相匹配的文本來替換這些字符。
例,單書名號包裹文本改為書名號。'<JavaScript>和<正則表達式>'.replace(/<([^_]*?)>/g, '《$1》')
會得到 "《JavaScript》和《正則表達式》"
使用字符串作為參數時替換字符串可以插入下面的特殊變量名:
$$
插入一個"$"
$&
插入匹配的子串。- `$`` 插入當前匹配的子串左邊的內容。
$'
插入當前匹配的子串右邊的內容。$n
假如第一個參數是 RegExp對象,並且 n 是個小於100的非負整數,那么插入第 n 個括號匹配的字符串。提示:索引是從1開始
使用函數作為第二個參數
function replacer(match, p1, p2, p3, offset, string) { }
// match 匹配的子串。
// p1,p2, ... 假如replace()方法的第一個參數是一個RegExp 對象,則代表第n個括號匹配的字符串。
// offset 匹配到的子字符串在原字符串中的偏移量。子串首字母下標。
// string 被匹配的原字符串。
例,下划線命名轉駝峰命名。'a_simple_name'.replace(/_([a-z])/g, (m, p1) => p1.toUpperCase())
將得到 "aSimpleName"
。
String.prototype.match(regexp)
參數 regexp
為一個正則表達式對象。如果傳入一個非正則表達式對象,則會隱式地使用 new RegExp(obj)
將其轉換為一個 RegExp
。
如果 regexp
沒有設置修飾符 g
,則僅返回第一個完整匹配及其相關的捕獲組(Array),返回數組第一個字符是匹配字符串,余下的元素是正則表達式中圓括號括起來的子表達式。在這種情況下,返回的項目將具有如下所述的其他屬性(groups: 一個捕獲組數組 或 undefined
(如果沒有定義命名捕獲組)。index: 匹配的結果的開始位置。input: 搜索的字符串。),或者未匹配時返回 null
。
如果使用 g
標志,則將返回與完整正則表達式匹配的所有結果,但不會返回捕獲組,或者未匹配時返回 null
。
'196.168.0.1'.match(/(\d+)(?=.|$)/) // (?=.|$) 先行匹配 匹配 . 或者字符串結尾
// (2) ["196", "196", index: 0, input: "196.168.0.1", groups: undefined]
'196.168.0.1'.match(/(?<num>\d+)(?=.|$)/) // (?<name>) 具名引用 見上文
// (2) ["196", "196", index: 0, input: "196.168.0.1", groups: {num: "196"}]
'196.168.0.1'.match(/\d+(?=.|$)/g)
// (4) ["196", "168", "0", "1"]
String.prototype.split([separator[, limit]])
separator
指定表示每個拆分應發生的點的字符串,可以是一個字符串或正則表達式。如果空字符串(""
)被用作分隔符,則字符串會在每個字符之間分割。
limit
一個整數,限定返回的分割片段數量。
例,'張三;李四,王五|趙六'.split(/[;\|,]/) // (4) ["張三", "李四", "王五", "趙六"]
RegExp 的屬性
flags
會返回正則表達式的修飾符。- 表示對應修飾符是否存在的只讀布爾值,
global
(表示是否帶有修飾符g
),ignoreCase
(i
),multiline
(m
),sticky
(y
),dotAll
(s
),unicode
(u
) source
只讀字符串,包含正則表達式的文本。lastIndex
可讀/寫整數。如果帶有g
修飾符,這個屬性存儲在整個字符串中下一次檢索的開始位置。這個屬性會被exec()
和test()
方法用到。
RegExp 的方法
exec()
如果沒有找到任何屬性,將返回 null
,如果找到匹配返回一個數組,該數組第一個元素是相匹配的字符串,余下的元素是與圓括號內的子表達式相匹配的子串。
當調用 exec()
的正則表達式具有修飾符 g
時,它將把當前正則表達式對象的 lastIndex
屬性設置為緊挨着匹配子串的字符位置。
注意即使兩次匹配的不是同一個字符串,lastIndex
還是會連續生效的。
let reg = /\d+/g;
reg.exec('25*10=250'); // ["25", index: 0, input: "25*10=250", groups: undefined]
reg.lastIndex; // 2
reg.exec('666'); // ["6", index: 2, input: "666", groups: undefined]
reg.lastIndex; // 3
test()
調用 test()
和 exec()
等價,當 exec()
返回結果不是 null
,test()
返回 true
,否則返回 false
。
String 的方法不會用到 lastIndex
屬性。