在正則表達(dá)式中讨盒,括號(hào)涉及的問題比較多苏潜,所以這里單獨(dú)拿出來講。
分組
如果量詞所限定的元素不是一個(gè)字符或者字符組拔疚,而是一系列字符或者子表達(dá)式肥隆,就需要使用括號(hào)將他們括起來,表示為“一組”稚失,構(gòu)成單個(gè)元素栋艳。
var regex = /(ab)+/g;
var string = "ababa abbb ababab";
console.log( string.match(regex) );
// ["abab", "ab", "ababab"]
上面的例子中,量詞 +
的前面的元素是 (ab)
句各, 所以 +
所限定的是括號(hào)內(nèi) ab
這個(gè)整體吸占。
劃定多選結(jié)構(gòu)的范圍
多選結(jié)構(gòu), 也叫 分支結(jié)構(gòu)凿宾。一般的用法: (p1|p2|p3)
矾屯,其中,|
表示 “或”初厚,p1
件蚕、p2
和 p3
是三個(gè)子表達(dá)式,這些子表達(dá)式也叫多選分支产禾, 括號(hào)用來劃定分支結(jié)構(gòu)的范圍排作。
注意:多選結(jié)構(gòu)中括號(hào)不是必須的。如果沒有括號(hào)亚情,管道符 |
會(huì)把整個(gè)表達(dá)式當(dāng)做一個(gè)多選結(jié)構(gòu)妄痪。比如,要匹配 grey或gray:
var regexRight = /gr(e|a)y/; // 匹配 grey 或 gray
var regexWrong = /gre|ay/; // 匹配 gre 或 ay
// 正確的
console.log(regexRight.test('grey')); // true
console.log(regexRight.test('gray')); // true
console.log(regexRight.test('gre')); // false
// 錯(cuò)誤的
console.log(regexWrong.test('grey')); // true
console.log(regexWrong.test('gre')); // true
所以楞件,雖然多選結(jié)構(gòu)中括號(hào)不是必須的拌夏,但是,通常會(huì)搭配括號(hào)來使用履因。
多選結(jié)構(gòu)與字符組
上面多選結(jié)構(gòu)中 gr(e|a)y
的例子并太好障簿,因?yàn)榭梢允褂酶玫姆绞酱妫潜闶?gr[ae]y
栅迄,那么二者什么區(qū)別呢站故?
二者差別還是很大的:
- 多選結(jié)構(gòu)中每個(gè)分支都必須明確列出。而字符組可以使用
-
表示范圍 - 大多數(shù)情況下,
[abc]
要比(a|b|c)
更高效 - 字符組的每個(gè) “分支” 都必須是單個(gè)的字符西篓,而多選結(jié)構(gòu)的“分支”可以是子表達(dá)式
- 多選結(jié)構(gòu)的分支順序會(huì)影響到最后的配置結(jié)果
- 沒有 排除型多選結(jié)構(gòu)
引用分組
使用括號(hào)之后愈腾,正則表示會(huì)保存每個(gè)分組真正匹配的文本,等匹配成功后岂津,可以引用這些文本虱黄。
因?yàn)檫@種情況下“捕獲”了文本,所以這種分組叫 捕獲分組吮成,這種括號(hào)叫 捕獲型括號(hào)橱乱。
通過編號(hào)引用
編號(hào)規(guī)則:
如,使用(\d{4})-(\d{2})-(\d{2})
匹配日期 2018-12-30
:
年 | 月 | 日 | |
---|---|---|---|
字符串 | 2018 | 12 | 30 |
表達(dá)式 | (\d{4}) |
(\d{2}) |
(\d{2}) |
分組編號(hào) | 1 | 2 | 3 |
注意:
如果把表達(dá)式寫成:(\d){4}-(\d){2}-(\d){2}
粱甫,則含義完全不同泳叠,(\d){4}
表示 \d
作為單獨(dú)的元素出現(xiàn)4次,且編號(hào)都為1茶宵。
嵌套規(guī)則:根據(jù)開括號(hào)的出現(xiàn)順序來計(jì)數(shù)危纫。(圖參考《正則指引》P45,我畫的有點(diǎn)丑)
在 JavaScript 中使用
提取數(shù)據(jù)
String.prototype.match()
方法返回一個(gè)數(shù)組乌庶,數(shù)組的第一項(xiàng)是進(jìn)行匹配的完整字符串种蝶,之后的項(xiàng)是捕獲分組的匹配結(jié)果。
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var text = '2018-12-30';
console.log(text.match(regex));
// ["2018-12-30", "2018", "12", "30", index: 0, input: "2018-12-30"]
關(guān)于 match
方法瞒大,有一個(gè)地方需要注意蛤吓,返回結(jié)果與正則表達(dá)式是否包含 g
標(biāo)志有關(guān)。在沒有 g
標(biāo)志的時(shí)候糠赦,返回值和 regex.exec()
方法相同:
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var text = '2018-12-30';
console.log(regex.exec(text));
// ["2018-12-30", "2018", "12", "30", index: 0, input: "2018-12-30"]
同時(shí)会傲,也可以使用構(gòu)造函數(shù)的全局屬性 $1
至 $9
來獲取引用:
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var text = '2018-12-30';
regex.exec(text);
console.log(RegExp.$1); // 2018
console.log(RegExp.$2); // 12
console.log(RegExp.$3); // 30
替換
比如,想把 yyyy-mm-dd
格式拙泽,替換成 mm/dd/yyyy
怎么做?
可以使用下面的三種方法:
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var text = '2018-12-30';
// 1
var result1 = text.replace(regex, '$2/$3/$1');
// 2
var result2 = text.replace(regex, () => `${RegExp.$2}/${RegExp.$3}/${RegExp.$1}`);
// 3
var result3 = text.replace(regex, (str, y, m, d) => `${m}/$zzdzsue/${y}`);
console.log(result1); // 12/30/2018
console.log(result2); // 12/30/2018
console.log(result3); // 12/30/2018
String.prototype.replace()
規(guī)則相對復(fù)雜淌山,有很多玩法,了解更多 顾瞻。
反向引用
在正則表達(dá)式內(nèi)部引用之前(左側(cè))捕獲分組匹配的文本泼疑,形式如:\num
,其中 num 表示編號(hào)荷荤,編號(hào)規(guī)則與之前介紹的相同退渗。
舉個(gè)例子:
比如要匹配: 2018-12-30
、2018.12.30
和 2018/12/30
三種形式蕴纳。
可能首先想到的是:\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}
会油,但是:
var regex = /\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/;
var text = '2018-12.30';
console.log(regex.test(text)); // true
顯然,我們不希望匹配 2018-12.30
古毛,我們需要前后的分隔符相同:
var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
var text1 = '2018-12.30';
var text2 = '2018-12-30';
var text3 = '2018/12/30';
console.log(regex.test(text1)); // false
console.log(regex.test(text2)); // true
console.log(regex.test(text3)); // true
這里的 \1
就是對前面 (-|\/|\.)
的引用翻翩,表達(dá)式可視化如下:
反向引用的二義性:
在反向引用中都许,如果編號(hào)大于9就會(huì)出現(xiàn)二義性,如:\10
是表示第十個(gè)捕獲分組呢還是表示第一個(gè)捕獲分組和一個(gè)字符 0
呢嫂冻?
在一些編程語言中有專門的規(guī)定來避免二義性胶征,但是在JavaScript中并沒有,JavaScript對于 \10
的處理是:
- 如果存在第 10 個(gè)捕獲分組桨仿,則引用對應(yīng)的分組
- 如果不存在睛低,則引用
\1
如果,在有第 10 個(gè)捕獲分組的情況下服傍,要匹配 \1
和 字符0
的話钱雷,可以使用下面兩種方法:
- 命名分組
- 再使用括號(hào)將
\1
或0
括起來,比如(\1)0
或\1(?:0)
命名分組
由于按編號(hào)引用分組存在一些問題伴嗡,如:可讀性差急波,不易維護(hù)从铲,二義性等瘪校。于是出現(xiàn)了命名分組,使用易記憶名段,易辨別的名字來代替編號(hào)阱扬。
注意:命名分組是 ES2017 新特性。
語法規(guī)則如下:
- 分組:
(?<name>)
- 提壬毂佟:
$<name>
- 反向引用:
\k<name>
比如麻惶,上文的一個(gè)例子可以改為:
var regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
var text = '2018-12-30';
var result = text.replace(regex, '$<month>/$<day>/$<year>');
console.log(result); // 12/30/2018
對于方法 String.prototype.match()
和 RegExp.prototype.exec()
也有了新玩法:
var regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
var text = '2018-12-30';
var matchObj = text.match(regex);
console.log(matchObj.groups);
// {year: "2018", month: "12", day: "30"}
在匹配結(jié)果中,多了 groups
屬性信夫,保存了所有命名捕獲分組的匹配結(jié)果窃蹋。
再來看一個(gè)反向引用的例子:
var regex = /\d{4}(?<split>-|\/|\.)\d{2}\k<split>\d{2}/;
var text = '2018-12-30';
console.log(regex.test(text)); // true
非捕獲分組
括號(hào)的功能有“疊加”性。括號(hào)可以表示分組静稻,用來構(gòu)成單個(gè)元素警没;也可以表示多選結(jié)構(gòu);但同時(shí)振湾,也構(gòu)成了引用分組杀迹。
在僅僅需要標(biāo)記范圍(分組或多選結(jié)構(gòu))時(shí),正則表達(dá)式保存已經(jīng)匹配的文本會(huì)造成不必要的性能浪費(fèi)押搪。
這時(shí)候我們可以使用 非捕獲型括號(hào) (?:...)
來限定分組或多選結(jié)構(gòu)的范圍:(?:p)
和 (?:p1|p2)
树酪。這種只用來限定范圍不捕獲匹配文本的分組就是 非捕獲分組。
非捕獲型分組的優(yōu)點(diǎn)是性能好大州,缺點(diǎn)是不美觀续语,可讀性差。
在實(shí)際應(yīng)用中厦画,建議盡量使用非捕獲分組绵载。