RegExp 對象
RegExp 對象表示正則表達式,它是對字符串執行模式匹配的強大工具。
直接量語法
/pattern/attributes
創建 RegExp 對象的語法:
new RegExp(pattern, attributes);
參數
參數 pattern 是一個字符串,指定了正則表達式的模式或其他正則表達式。
參數 attributes 是一個可選的字符串,包含屬性 "g"、"i" 和 "m",分別用於指定全局匹配、區分大小寫的匹配和多行匹配。ECMAScript 標準化之前,不支持 m 屬性。如果 pattern 是正則表達式,而不是字符串,則必須省略該參數。
返回值
一個新的 RegExp 對象,具有指定的模式和標誌。如果參數 pattern 是正則表達式而不是字符串,那麼 RegExp() 構造函數將用與指定的 RegExp 相同的模式和標誌創建一個新的 RegExp 對象。
如果不用 new 運算符,而將 RegExp() 作爲函數調用,那麼它的行爲與用 new 運算符調用時一樣,只是當 pattern 是正則表達式時,它只返回 pattern,而不再創建一個新的 RegExp 對象。
拋出
SyntaxError - 如果 pattern 不是合法的正則表達式,或 attributes 含有 "g"、"i" 和 "m" 之外的字符,拋出該異常。
TypeError - 如果 pattern 是 RegExp 對象,但沒有省略 attributes 參數,拋出該異常。
修飾符
修飾符 | 描述 |
---|---|
i | 執行對大小寫不敏感的匹配。 |
g | 執行全局匹配(查找所有匹配而非在找到第一個匹配後停止)。 |
m | 執行多行匹配。 |
新特性:
u | 含義爲“Unicode 模式”,用來正確處理大於\uFFFF 的 Unicode 字符。也就是說,會正確處理四個字節的 UTF-16 編碼。 |
y | “粘連”(sticky)修飾符,確保匹配必須從剩餘的第一個位置開始,這也就是“粘連”的涵義。 |
s | ES2018 引入s 修飾符,使得. 可以匹配任意單個字符。 |
方括號
方括號用於查找某個範圍內的字符:
表達式 | 描述 |
---|---|
[abc] | 查找方括號之間的任何字符。 |
[^abc] | 查找任何不在方括號之間的字符。 |
[0-9] | 查找任何從 0 至 9 的數字。 |
[a-z] | 查找任何從小寫 a 到小寫 z 的字符。 |
[A-Z] | 查找任何從大寫 A 到大寫 Z 的字符。 |
[A-z] | 查找任何從大寫 A 到小寫 z 的字符。 |
[adgk] | 查找給定集合內的任何字符。 |
[^adgk] | 查找給定集合外的任何字符。 |
(red|blue|green) | 查找任何指定的選項。 |
元字符
元字符(Metacharacter)是擁有特殊含義的字符:
元字符 | 描述 |
---|---|
. | 查找單個字符,除了換行和行結束符。 |
\w | 查找單詞字符。 |
\W | 查找非單詞字符。 |
\d | 查找數字。 |
\D | 查找非數字字符。 |
\s | 查找空白字符。 |
\S | 查找非空白字符。 |
\b | 匹配單詞邊界。 |
\B | 匹配非單詞邊界。 |
\0 | 查找 NUL 字符。 |
\n | 查找換行符。 |
\f | 查找換頁符。 |
\r | 查找回車符。 |
\t | 查找製表符。 |
\v | 查找垂直製表符。 |
\xxx | 查找以八進制數 xxx 規定的字符。 |
\xdd | 查找以十六進制數 dd 規定的字符。 |
\uxxxx | 查找以十六進制數 xxxx 規定的 Unicode 字符。 |
量詞
量詞 | 描述 |
---|---|
n+ | 匹配任何包含至少一個 n 的字符串。 |
n* | 匹配任何包含零個或多個 n 的字符串。 |
n? | 匹配任何包含零個或一個 n 的字符串。 |
n{X} | 匹配包含 X 個 n 的序列的字符串。 |
n{X,Y} | 匹配包含 X 至 Y 個 n 的序列的字符串。 |
n{X,} | 匹配包含至少 X 個 n 的序列的字符串。 |
n$ | 匹配任何結尾爲 n 的字符串。 |
^n | 匹配任何開頭爲 n 的字符串。 |
?=n | 匹配任何其後緊接指定字符串 n 的字符串。 |
?!n | 匹配任何其後沒有緊接指定字符串 n 的字符串。 |
RegExp 對象屬性
屬性 | 描述 | FF | IE |
---|---|---|---|
global | RegExp 對象是否具有標誌 g。 | 1 | 4 |
ignoreCase | RegExp 對象是否具有標誌 i。 | 1 | 4 |
lastIndex | 一個整數,標示開始下一次匹配的字符位置。 | 1 | 4 |
multiline | RegExp 對象是否具有標誌 m。 | 1 | 4 |
source | 正則表達式的源文本。 | 1 | 4 |
新特性:
unicode | 是否設置了“u”修飾符。 |
sticky | 是否設置了“y”修飾符。 |
flags | 返回正則表達式的修飾符。 |
RegExp 對象方法
方法 | 描述 | FF | IE |
---|---|---|---|
compile | 編譯正則表達式。 | 1 | 4 |
exec | 檢索字符串中指定的值。返回找到的值,並確定其位置。 | 1 | 4 |
test | 檢索字符串中指定的值。返回 true 或 false。 | 1 | 4 |
支持正則表達式的 String 對象的方法
方法 | 描述 | FF | IE |
---|---|---|---|
search | 檢索與正則表達式相匹配的值。 | 1 | 4 |
match | 找到一個或多個正則表達式的匹配。 | 1 | 4 |
replace | 替換與正則表達式匹配的子串。 | 1 | 4 |
split | 把字符串分割爲字符串數組。 | 1 | 4 |
新特性:
matchAll | 提案階段,返回一個遍歷器而不是一個數組 |
新的特性
1、後行斷言:
JavaScript 語言的正則表達式,只支持先行斷言(lookahead)和先行否定斷言(negative lookahead),不支持後行斷言(lookbehind)和後行否定斷言(negative lookbehind)。ES2018 引入後行斷言,V8 引擎 4.9 版(Chrome 62)已經支持。
”先行斷言“指的是,x
只有在y
前面才匹配,必須寫成/x(?=y)/
。比如,只匹配百分號之前的數字,要寫成/\d+(?=%)/
。”先行否定斷言“指的是,x
只有不在y
前面才匹配,必須寫成/x(?!y)/
。比如,只匹配不在百分號之前的數字,要寫成/\d+(?!%)/
。
/\d+(?=%)/.exec('100% of US presidents have been male') // ["100"]
/\d+(?!%)/.exec('that’s all 44 of them') // ["44"]
上面兩個字符串,如果互換正則表達式,就不會得到相同結果。另外,還可以看到,”先行斷言“括號之中的部分((?=%)
),是不計入返回結果的。
“後行斷言”正好與“先行斷言”相反,x
只有在y
後面才匹配,必須寫成/(?<=y)x/
。比如,只匹配美元符號之後的數字,要寫成/(?<=\$)\d+/
。”後行否定斷言“則與”先行否定斷言“相反,x
只有不在y
後面才匹配,必須寫成/(?<!y)x/
。比如,只匹配不在美元符號後面的數字,要寫成/(?<!\$)\d+/
。
/(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill') // ["100"]
/(?<!\$)\d+/.exec('it’s is worth about €90') // ["90"]
上面的例子中,“後行斷言”的括號之中的部分((?<=\$)
),也是不計入返回結果。
下面的例子是使用後行斷言進行字符串替換。
const RE_DOLLAR_PREFIX = /(?<=\$)foo/g;
'$foo %foo foo'.replace(RE_DOLLAR_PREFIX, 'bar');
// '$bar %foo foo'
上面代碼中,只有在美元符號後面的foo
纔會被替換。
“後行斷言”的實現,需要先匹配/(?<=y)x/
的x
,然後再回到左邊,匹配y
的部分。這種“先右後左”的執行順序,與所有其他正則操作相反,導致了一些不符合預期的行爲。
首先,後行斷言的組匹配,與正常情況下結果是不一樣的。
/(?<=(\d+)(\d+))$/.exec('1053') // ["", "1", "053"]
/^(\d+)(\d+)$/.exec('1053') // ["1053", "105", "3"]
上面代碼中,需要捕捉兩個組匹配。沒有“後行斷言”時,第一個括號是貪婪模式,第二個括號只能捕獲一個字符,所以結果是105
和3
。而“後行斷言”時,由於執行順序是從右到左,第二個括號是貪婪模式,第一個括號只能捕獲一個字符,所以結果是1
和053
。
其次,“後行斷言”的反斜槓引用,也與通常的順序相反,必須放在對應的那個括號之前。
/(?<=(o)d\1)r/.exec('hodor') // null
/(?<=\1d(o))r/.exec('hodor') // ["r", "o"]
上面代碼中,如果後行斷言的反斜槓引用(\1
)放在括號的後面,就不會得到匹配結果,必須放在前面纔可以。因爲後行斷言是先從左到右掃描,發現匹配以後再回過頭,從右到左完成反斜槓引用。
2、Unicode 屬性類
ES2018 引入了一種新的類的寫法\p{...}
和\P{...}
,允許正則表達式匹配符合 Unicode 某種屬性的所有字符。
const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π') // true
上面代碼中,\p{Script=Greek}
指定匹配一個希臘文字母,所以匹配π
成功。
Unicode 屬性類要指定屬性名和屬性值。
\p{UnicodePropertyName=UnicodePropertyValue}
對於某些屬性,可以只寫屬性名,或者只寫屬性值。
\p{UnicodePropertyName}
\p{UnicodePropertyValue}
\P{…}
是\p{…}
的反向匹配,即匹配不滿足條件的字符。
注意,這兩種類只對 Unicode 有效,所以使用的時候一定要加上u
修飾符。如果不加u
修飾符,正則表達式使用\p
和\P
會報錯,ECMAScript 預留了這兩個類。
由於 Unicode 的各種屬性非常多,所以這種新的類的表達能力非常強。
const regex = /^\p{Decimal_Number}+$/u;
regex.test('𝟏𝟐𝟑𝟜𝟝𝟞𝟩𝟪𝟫𝟬𝟭𝟮𝟯𝟺𝟻𝟼') // true
上面代碼中,屬性類指定匹配所有十進制字符,可以看到各種字型的十進制字符都會匹配成功。
\p{Number}
甚至能匹配羅馬數字。
// 匹配所有數字
const regex = /^\p{Number}+$/u;
regex.test('²³¹¼½¾') // true
regex.test('㉛㉜㉝') // true
regex.test('ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ') // true
下面是其他一些例子。
// 匹配所有空格
\p{White_Space}
// 匹配各種文字的所有字母,等同於 Unicode 版的 \w
[\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]
// 匹配各種文字的所有非字母的字符,等同於 Unicode 版的 \W
[^\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]
// 匹配 Emoji
/\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu
// 匹配所有的箭頭字符
const regexArrows = /^\p{Block=Arrows}+$/u;
regexArrows.test('←↑→↓↔↕↖↗↘↙⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇧⇩') // true
3、具名組匹配
正則表達式使用圓括號進行組匹配。
const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;
上面代碼中,正則表達式裏面有三組圓括號。使用exec
方法,就可以將這三組匹配結果提取出來。
const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj[1]; // 1999
const month = matchObj[2]; // 12
const day = matchObj[3]; // 31
組匹配的一個問題是,每一組的匹配含義不容易看出來,而且只能用數字序號(比如matchObj[1]
)引用,要是組的順序變了,引用的時候就必須修改序號。
ES2018 引入了具名組匹配(Named Capture Groups),允許爲每一個組匹配指定一個名字,既便於閱讀代碼,又便於引用。
const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31
上面代碼中,“具名組匹配”在圓括號內部,模式的頭部添加“問號 + 尖括號 + 組名”(?<year>
),然後就可以在exec
方法返回結果的groups
屬性上引用該組名。同時,數字序號(matchObj[1]
)依然有效。
具名組匹配等於爲每一組匹配加上了 ID,便於描述匹配的目的。如果組的順序變了,也不用改變匹配後的處理代碼。
如果具名組沒有匹配,那麼對應的groups
對象屬性會是undefined
。
const RE_OPT_A = /^(?<as>a+)?$/;
const matchObj = RE_OPT_A.exec('');
matchObj.groups.as // undefined
'as' in matchObj.groups // true
上面代碼中,具名組as
沒有找到匹配,那麼matchObj.groups.as
屬性值就是undefined
,並且as
這個鍵名在groups
是始終存在的。
解構賦值和替換:
有了具名組匹配以後,可以使用解構賦值直接從匹配結果上爲變量賦值。
let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
one // foo
two // bar
字符串替換時,使用$<組名>
引用具名組。
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
'2015-01-02'.replace(re, '$<day>/$<month>/$<year>')
// '02/01/2015'
上面代碼中,replace
方法的第二個參數是一個字符串,而不是正則表達式。
replace
方法的第二個參數也可以是函數,該函數的參數序列如下。
'2015-01-02'.replace(re, (
matched, // 整個匹配結果 2015-01-02
capture1, // 第一個組匹配 2015
capture2, // 第二個組匹配 01
capture3, // 第三個組匹配 02
position, // 匹配開始的位置 0
S, // 原字符串 2015-01-02
groups // 具名組構成的一個對象 {year, month, day}
) => {
let {day, month, year} = groups;
return `${day}/${month}/${year}`;
});
具名組匹配在原來的基礎上,新增了最後一個函數參數:具名組構成的一個對象。函數內部可以直接對這個對象進行解構賦值。
引用:
如果要在正則表達式內部引用某個“具名組匹配”,可以使用\k<組名>
的寫法。
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false
數字引用(\1
)依然有效。
const RE_TWICE = /^(?<word>[a-z]+)!\1$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false
這兩種引用語法還可以同時使用。
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>!\1$/;
RE_TWICE.test('abc!abc!abc') // true
RE_TWICE.test('abc!abc!ab') // false