【正則】954- 正則表達(dá)式有幾種字符匹配模式?


最近再一次重溫老姚大佬的《JavaScript 正則表達(dá)式迷你書》 , 并將核心知識(shí)點(diǎn)整理一遍,方便復(fù)習(xí)。
原書寫得非常棒,建議看下原書啦。 地址:https://github.com/qdlaoyao/js-regex-mini-book
原書這么一句話,特別棒:正則表達(dá)式是匹配模式,要么匹配字符,要么匹配位置,要記住。
1. 兩種模糊匹配
正則表達(dá)式的強(qiáng)大在于它的模糊匹配,這里介紹兩個(gè)方向上的“模糊”:橫向模糊和縱向模糊。
橫向模糊匹配
即一個(gè)正則可匹配的字符串長(zhǎng)度不固定,可以是多種情況。
如 /ab{2,5}c/ 表示匹配:第一個(gè)字符是 "a" ,然后是 2 - 5 個(gè)字符 "b" ,最后是字符 "c":
let r = /ab{2,5}c/g;
let s = "abc abbc abbbc abbbbbbc";
s.match(r); // ["abbc", "abbbc"]
縱向模糊匹配
即一個(gè)正則可匹配某個(gè)不確定的字符,可以有多種可能。
如 /[abc]/ 表示匹配 "a", "b", "c" 中任意一個(gè)。
let r = /a[123]b/g;
let s = "a0b a1b a4b";
s.match(r); // ["a1b"]
2. 字符組
范圍表示法
可以指定字符范圍,比如 [1234abcdUVWXYZ] 就可以表示成 [1-4a-dU-Z] ,使用 - 來進(jìn)行縮寫。
如果要匹配 "a", "-", "z" 中任意一個(gè)字符,可以這么寫:[-az] 或 [a\-z] 或 [az-] 。
排除字符組
即需要排除某些字符時(shí)使用,通過在字符組第一個(gè)使用 ^ 來表示取反,如 [^abc] 就表示匹配除了 "a", "b", "c" 的任意一個(gè)字符。
常見簡(jiǎn)寫形式
| 字符組 | 具體含義 |
|---|---|
\d | 表示 [0-9],表示一位數(shù)字。 |
\D | 表示 [^0-9],表示除數(shù)字外的任意字符。 |
\w | 表示 [0-9a-zA-Z_],表示數(shù)字、大小寫字母和下劃線。 |
\W | 表示 [^0-9a-zA-Z_],表示非單詞字符。 |
\s | 表示 [\t\v\n\r\f],表示空白符,包含空格、水平制表符、垂直制表符、換行符、回車符、換頁(yè)符。 |
\S | 表示 [^\t\v\n\r\f],表示非空白字符。 |
. | 表示 [^\n\r\u2028\u2029] 。通配符,表示幾乎任意字符。換行符、回車符、行分隔符和段分隔符除外。 |
然后表示任意字符,就可以使用 [\d\D]、[\w\W]、[\s\S] 和 [^] 任意一個(gè)。
3. 量詞
量詞也稱重復(fù),常用簡(jiǎn)寫如下:
| 量詞 | 具體含義 |
|---|---|
{m,} | 表示至少出現(xiàn) m 次。 |
{m} | 等價(jià)于 {m, m} ,表示出現(xiàn) m 次。 |
? | 等價(jià)于 {0, 1} ,表示出現(xiàn)或不出現(xiàn)。 |
+ | 等價(jià)于 {1, } ,表示至少出現(xiàn)1次。 |
* | 等價(jià)于 {0, } ,表示出現(xiàn)任意次,也有可能不出現(xiàn)。 |
貪婪匹配和惰性匹配
在正則 /\d{2,4}/ ,表示數(shù)字連續(xù)出現(xiàn) 2 - 4 次,可以匹配到 2 位、 3 位、4 位連續(xù)數(shù)字。
但是在 貪婪匹配 如 /\d{2,4}/g ,會(huì)盡可能多匹配,如超過 4 個(gè),就只匹配 4 個(gè),如有 3 個(gè),就匹配 3 位。
而在 惰性匹配 如 /\d{2,4}?/g ,會(huì) 盡可能少 匹配,如超過 2 個(gè),就只匹配 2 個(gè),不會(huì)繼續(xù)匹配下去。
let r1 = /\d{2,4}/g;
let r2 = /\d{2,4}?/g;
let s = "123 1234 12345";
s.match(r1); // ["123", "1234", "1234"]
s.match(r2); // ["12", "12", "34", "12", "34"]
| 惰性量詞 | 貪婪量詞 |
|---|---|
{m,m}? | {m,m} |
{m,}? | {m,} |
?? | ? |
+? | + |
*? | * |
4. 多選分支
即提供多個(gè)子匹配模式任選一個(gè),使用 |(管道符)分隔,由于分支結(jié)構(gòu)也是惰性,即匹配上一個(gè)后,就不會(huì)繼續(xù)匹配后續(xù)的。
格式如:(r1|r2|r3),我們就可以使用 /leo|pingan/ 來匹配 "leo" 和 "pingan"。
let r = /leo|pingan/g;
let s = "leo cool,pingan good.";
s.match(r);// ["leo", "pingan"]
// 多選分支的惰性表現(xiàn)
let r1 = /leo|leooo/g;
let r2 = /leooo|leo/g;
let s = "leooo";
s.match(r1);// ["leo"]
s.match(r2);// ["leooo"]
5. 分組命名 (ES9)
ES9 之前的分組是通過數(shù)字命名的:
const pattern = /(\d{4})-(\d{2})-(\d{2})/u;
const result = pattern.exec('2020-01-05');
console.log(result[0]); // 打印"2020-01-05"
console.log(result[1]); // 打印"2020"
console.log(result[2]); // 打印"01"
console.log(result[3]); // 打印"05"
現(xiàn)在可以通過指定分組的名稱,增加代碼可讀性,便于維護(hù):
const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
const result = pattern.exec('2020-01-05');
console.log(result.groups.year); // 打印"2020-01-05"
console.log(result.groups.month); // 打印"01"
console.log(result.groups.day); // 打印"05"
分組命名還可以和 String.prototype.replace 方法結(jié)合:
const reDate = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const d = '2020-01-05';
const USADate = d.replace(reDate, '$<month>-$<day>-$<year>');
console.log(USADate); // 01-05-2020
6. 忽略分組
如果不希望捕獲某些分組,在分組內(nèi)加上 ?: 即可。比如 (?:tom).(ok) 那么這里 $1 指的就是 ok。
7. 案例分析
匹配字符,無非就是字符組、量詞和分支結(jié)構(gòu)的組合使用。
十六進(jìn)制顏色值匹配
let r = /#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}/g;
let s = "#ffaacc #Ff00DD #fFF #01d #9Gd";
s.match(r); // ["#ffaacc", "#Ff00DD", "#fFF", "#01d"]
時(shí)間和日期匹配
// 時(shí)間 12:23 或 01:09
let r = /^([01][0-9]|[2][0-3]):[0-5][0-9]$/;
r.test("23:25"); // true
r.test("03:05"); // true
// 時(shí)間 12:23 或 1:9
let r = /^(0?[0-9]|1[0-9]|[2][0-3]):(0?[0-9]|[1-5][0-9])$/;
r.test("23:25"); // true
r.test("03:05"); // true
r.test("3:5"); // true
// 日期 yyyy-mm-dd
let r = /^[0-9]{4}-(0[1-9]|[1][0-2])-(0[1-9]|[12][0-9]|3[01])$/;
r.test("2019-09-19"); // true
r.test("2019-09-32"); // false
Windows操作系統(tǒng)文件路徑匹配
盤符使用 [a-zA-Z]:\\ ,這里需要注意 \ 字符需要轉(zhuǎn)義,并且盤符不區(qū)分大小寫;
文件名或文件夾名,不能包含特殊字符,使用 [^\\:*<>|"?\r\n/] 表示合法字符;
并且至少有一個(gè)字符,還有可以出現(xiàn)任意次,就可以使用 ([^\\:*<>|"?\r\n/]+\\)* 匹配任意個(gè) 文件夾\;
還有路徑最后一部分可以是 文件夾 ,即沒有 \ 于是表示成 ([^\\:*<>|"?\r\n/]+)?。
let r = /^[a-zA-Z]:\\([^\\:*<>|"?\r\n/]+\\)*([^\\:*<>|"?\r\n/]+)?$/;
r.test("C:\\document\\leo\\a.png"); // true
r.test("C:\\document\\leo\\"); // true
r.test("C:\\document"); // true
r.test("C:\\"); // true
id匹配
如提取 <div id="leo" class="good"></id> 中的 id="leo" :
let r1 = /id=".*"/; // tips1
let r2 = /id=".*?"/; // tips2
let r3 = /id="[^"]*"/; // tips3
let s = '<div id="leo" class="good"></id>';
s.match(r1)[0]; // id="leo" class="good"
s.match(r2)[0]; // id="leo"
s.match(r3)[0]; // id="leo"
tips1:由于 . 匹配雙引號(hào),且 * 貪婪,就會(huì)持續(xù)匹配到最后一個(gè)雙引號(hào)結(jié)束。
tips2:使用惰性匹配,但效率低,有回溯問題。
tips3:最終優(yōu)化。
