JavaScript中的正則表達式及一些新特性

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"]

上面代碼中,需要捕捉兩個組匹配。沒有“後行斷言”時,第一個括號是貪婪模式,第二個括號只能捕獲一個字符,所以結果是1053。而“後行斷言”時,由於執行順序是從右到左,第二個括號是貪婪模式,第一個括號只能捕獲一個字符,所以結果是1053

其次,“後行斷言”的反斜槓引用,也與通常的順序相反,必須放在對應的那個括號之前。

/(?<=(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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章