正則運算式從入門到入坑
入坑前先介紹兩個輔助網站:
正則運算式測驗網站:https://regex101.com
正則運算式思維導圖:https://regexper.com
正則基礎(入門)
1、元字符
進入正題,我們先去了解最基本的字符及其初步應用,
| 元字符 | 描述 |
|---|---|
| \ | 將下一個字符標記為一個特殊字符、或一個原義字符、或一個 向后參考、或一個八進制轉義符, |
| ^ | 匹配輸入字串的開始位置,如果設定了 RegExp 物件的 Multiline 屬性,^ 也匹配 '\n' 或 '\r' 之后的位置, |
| $ | 匹配輸入字串的結束位置,如果設定了RegExp 物件的 Multiline 屬性,$ 也匹配 '\n' 或 '\r' 之前的位置, |
| \b | 匹配一個單詞邊界,也就是指單詞和空格間的位置, |
| \B | 匹配非單詞邊界, |
| \d | 匹配一個數字字符,等價于 [0-9], |
| \D | 匹配一個非數字字符,等價于 [^0-9], |
| \f | 匹配一個換頁符, |
| \n | 匹配一個換行符, |
| \r | 匹配一個回車符, |
| \s | 匹配任何空白字符,包括空格、制表符、換頁符等等,等價于 [ \f\n\r\t\v], |
| \S | 匹配任何非空白字符,等價于 [^ \f\n\r\t\v], |
| \w | 匹配字母、數字、下劃線,等價于'[A-Za-z0-9_]', |
| \W | 匹配非字母、數字、下劃線,等價于 '[^A-Za-z0-9_]', |
不運用起來的知識都不是自己的知識,所以看完總得寫點例子建立思維記憶,
比如:
1、匹配有hello開頭的字串:
let str = "hello world";
// 方法一
let reg = /^hello/;
reg.test(str); //true
// 方法二
let reg2 = /\bhello/;
reg2.test(str); //true
這么一看\b和^好像功能差不多,其實不然,我們看下一個例子:
let str = "say hello";
let reg = /^hello/;
reg.test(str); //false
let reg2 = /\bhello/;
reg2.test(str); //true
可以看出\b并不是匹配開頭,它匹配的是單詞邊界,
2、匹配1開頭的11位數字的手機號碼:
let phone = "13388882983";
let reg = /^1\d\d\d\d\d\d\d\d\d\d$/
3、匹配8的數字、字母和下劃線組成的密碼:
let password = "A_1234_b"
let reg = /^\w\w\w\w\w\w\w\w$/
2、重復限定符
匹配每一個數字都得寫一個/d,代碼怎么可以這么冗余,我們追求的是優雅,那該怎么寫呢?我們先看下面的限定符,
| 語法 | 描述 |
|---|---|
| * | 匹配前面的子運算式零次或多次,* 等價于{0,}, |
| + | 匹配前面的子運算式一次或多次,+ 等價于 {1,}, |
| ? | 匹配前面的子運算式零次或一次,? 等價于 {0,1}, |
| {n} | n 是一個非負整數,匹配確定的 n 次, |
| {n,} | n 是一個非負整數,至少匹配n 次, |
| {n,m} | m 和 n 均為非負整數,其中n <= m,最少匹配 n 次且最多匹配 m 次,配n 次, |
看完我們對剛剛的正則進行一點點優雅的改造,
1、匹配8的數字、字母和下劃線組成的密碼:
let password = "A_1234_b"
let reg = /^\w{8}$/
但是產品覺得限制8位太不靈活了,它要8-15位,好,滿足它:
let password = "A_1234_b"
let reg = /^\w{8,15}$/
2、匹配以a開頭的,中間帶有一個或多個b,0個或多個c結尾的字串:
let reg = /^ab+c*$/;
let str = "abbc";
reg.test(str); //true
let str2 = "abb";
reg.test(str2); //true
let str3 = "acc";
reg.test(str3); //false
3、區間 []
產品想法越來越多,密碼希望只能給用戶設定由大小寫字母和數字組成的8-15位密碼,摸了摸刀柄,決定繼續滿足它,
let reg = /^[A-Za-z0-9]{8,15}$/;
let password = "A123456789b";
reg.test(password); //true
let password2 = "A_1234_b";
reg.test(password2); //false
4、條件或
產品給你點了個贊然后提出了手機號碼驗證要優化的想法,調查發現VIP客戶的手機只有13、156、176、186開頭的11位數,要我們進行精確一點匹配,看了一眼它更長的刀,默默的寫下下面的正則:
let reg = /^(13\d|156|176|186)\d{8}$/
產品表示很滿意,結束了它的基本需求,
5、修飾符
標記也稱為修飾符,正則運算式的標記用于指定額外的匹配策略,
標記不寫在正則運算式里,標記位于運算式之外,格式如下:
/pattern/flags
| 修飾符 | 含義 | 描述 |
|---|---|---|
| i | ignore - 不區分大小寫 | 將匹配設定為不區分大小寫,搜索時不區分大小寫: A 和 a 沒有區別, |
| g | global - 全域匹配 | 查找所有的匹配項, |
| m | multiline - 多行匹配 | 使邊界字符 ^ 和 $ 匹配每一行的開頭和結尾,記住是多行,而不是整個字串的開頭和結尾, |
| s | 特殊字符圓點 . 中包含換行符 \n | 默認情況下的圓點 . 是匹配除換行符 \n 之外的任何字符,加上 s 修飾符之后, . 中包含換行符 \n, |
let str = 'The fat cat eat the fish on the dish.'
let reg = /the/
str.match(reg); //["the",index:16]
通常正則匹配到第一個就會自動結束,因此我們只能匹配到the fish中的ths就結束了,如果我們希望把后面的"the"也匹配出來呢?這時候我們就要加一個全域匹配修飾符了,
let str = 'The fat cat eat the fish on the dish.'
let reg = /the/g
str.match(reg); //["the","the"]
要是希望把開頭大寫的"The"也一起匹配出來呢?這時候我們需要再加多一個全域匹配修飾符i,
let str = 'The fat cat eat the fish on the dish.'
let reg = /the/gi
str.match(reg); //["The","the","the"]
一般我們使用^或$只會匹配文章的開頭和結尾,
let str = 'The fat cat eat the fish on the dish.\nThe cat is beautiful.'
let reg = /^The/g
str.match(reg); //["The"]
但是如果我們需要匹配各個段落的開頭呢?
let str = 'The fat cat eat the fish on the dish.\nThe cat is beautiful.'
let reg = /^The/gm
str.match(reg); //["The","The"]
默認情況下的圓點 . 是匹配除換行符 \n 之外的任何字符,如:
let str = 'The fat cat eat the fish on the dish.\nThe cat is beautiful.'
let reg = /.+/
str.match(reg); //["The fat cat eat the fish on the dish.",...]
我們發現遇到\n的時候會切換了匹配,如果我們想繼續完全匹配下去,需要加上修飾符s,
let str = 'The fat cat eat the fish on the dish.\nThe cat is beautiful.'
let reg = /.+/s
str.match(reg); //['The fat cat eat the fish on the dish.\nThe cat is beautiful.',...]
6、運算子優先級
正則運算式從左到右進行計算,并遵循優先級順序,這與算術運算式非常類似,
相同優先級的從左到右進行運算,不同優先級的運算先高后低,下表從最高到最低說明了各種正則運算式運算子的優先級順序:
| 運算子 | 描述 |
|---|---|
| \ | 轉義符 |
| (), (?: ), (?=), [] | 圓括號和方括號 |
| *, +, ?, {n}, {n,}, {n,m} | 限定符 |
| ^, $, \任何元字符、任何字符 | 定位點和序列(即:位置和順序) |
| | | 替換,"或"操作 字符具有高于替換運算子的優先級,使得"m|food"匹配"m"或"food",若要匹配"mood"或"food",請使用括號創建子運算式,從而產生"(m|f)ood", |
Js正則常用方法
1、定義正則
定義正則有下面兩種方式:
// 第一種
//RegExp物件,引數就是我們想要制定的規則,
let reg = new RegExp("a");
// 第二種
// 簡寫方法 推薦使用 書寫簡便、性能更好,
let reg = /a/;
2、test()
在字串中查找符合正則的內容,若查找到回傳true,反之回傳false,
例:
let reg = /^ab+c*$/;
let str = "abbc";
reg.test(str); //true
3、match()
在字串中搜索復合規則的內容,搜索成功就回傳內容,格式為陣列,失敗就回傳null,
例:
let str = 'The fat cat eat the fish on the dish.';
let reg = /the/;
str.match(reg); //["the",index:16,...]
全域匹配匹配到多個是則在陣列中按序回傳,如:
let str = 'The fat cat eat the fish on the dish.';
let reg = /the/g;
str.match(reg); //["the","the"]
4、search()
在字串搜索符合正則的內容,搜索到就回傳坐標(從0開始,如果匹配的不只是一個字母,那只會回傳第一個字母的位置), 如果搜索失敗就回傳 -1 ,
例:
let str = 'abc';
let reg = /bc/;
str.search(reg); //1
5、exec()
和match方法一樣,搜索符合規則的內容,并回傳內容,格式為陣列,
let str = 'The fat cat eat the fish on the dish.';
let reg = /the/;
reg.exec(str); //["the",index:16]
如果是全域匹配,可以通過while回圈 找到每次匹配到的資訊,如:
let str = 'The fat cat eat the fish on the dish.';
let reg = /the/g;
let res = "";
while(res = reg.exec(str)){
console.log(res);
}
/**
* 匹配到兩次
* 第一次:
* [
0: "the"
groups: undefined
index: 16
input: "The fat cat eat the fish on the dish."
* ]
*
* 第二次:
* [
0: "the"
groups: undefined
index: 28
input: "The fat cat eat the fish on the dish."
* ]
*/
6、replace()
查找符合正則的字串,就替換成對應的字串,回傳替換后的內容,
replace方法接收兩個引數,第一個是正則,第二個是替換字符/回呼方法,我們下面分別舉例說明:
例1:
let str = 'abc';
let reg = /a/;
str.replace(reg,"A"); //"Abc"
例2:
let str = 'abc';
let reg = /a/;
str.replace(reg,function(res){
console.log(res); //'a'
return "A"; //不return則會回傳undefine,輸出結果則會變成"undefinedbc",
}); //"Abc"
除此以外replace還有更深入的用法,會放在后面入坑那里再說,
正則進階(入坑)
1、零寬斷言
我們先去理解零寬和斷言分別是什么,
--零寬:就是沒有寬度,在正則中,斷言只是匹配位置,不占字符,也就是說,匹配結果里是不會回傳斷言本身,
--斷言:俗話的斷言就是“我斷定什么什么”,而正則中的斷言,就是說正則可以指明在指定的內容的前面或后面會出現滿足指定規則的內容,
總結:
零寬斷言正如它的名字一樣,是一種零寬度的匹配,它匹配到的內容不會保存到匹配結果中去,最終匹配結果只是一個位置而已,
javascript只支持零寬先行斷言,而零寬先行斷言又可以分為正向零寬先行斷言,和負向零寬先行斷言,
1、 正向先行斷言(正向肯定預查):
--語法:(?=pattern)
--作用:匹配pattern運算式的前面內容,不回傳本身,
我們來舉個栗子:
The fat cat eat the fish on the dish.
我們希望拿到fat前面的字串The,
let str = 'The fat cat eat the fish on the dish.'
let reg = /the(?=\sfat)/gi
str.match(reg); //["The"]
2、負向先行斷言(正向否定預查):
--語法:(?!pattern)
--作用:匹配pattern運算式的前面內容,不回傳本身,
那如果我們希望拿到不是fat前面的字串The呢?很簡單:
let str = 'The fat cat eat the fish on the dish.'
let reg = /the(?!\sfat)/gi
str.match(reg); //["the","the"]
3、正向后行斷言(反向肯定預查):
--語法:(?<=pattern)
--作用:匹配pattern運算式的后面的內容,不回傳本身,
繼續舉個栗子:
This is the flower cat and the civet cat.
我們希望拿到flower后面的cat,
let str = `This is the flower cat and the civet cat.`
let reg = /(?<=flower\s)cat/
str.match(reg); //["cat",index:19]
4、 負向后行斷言(反向否定預查)
--語法:(?<!pattern)
--作用:匹配非pattern運算式的后面內容,不回傳本身,
那如果我們希望拿到不是flower后面的cat呢?
let str = `This is the flower cat and the civet cat.`
let reg = /(?<!flower\s)cat/
str.match(reg); //["cat",index:37]
2、捕獲和非捕獲
單純說到捕獲,他的意思是匹配運算式,但捕獲通常和分組聯系在一起,也就是“捕獲組”,
捕獲組:匹配子運算式的內容,把匹配結果保存到記憶體中中數字編號或顯示命名的組里,以深度優先進行編號,之后可以通過序號或名稱來使用這些匹配結果,
而根據命名方式的不同,又可以分為兩種組:
1、數字編號捕獲組:
語法:(exp)
解釋:從運算式左側開始,每出現一個左括號和它對應的右括號之間的內容為一個分組,在分組中,第0組為整個運算式,第一組開始為分組,
舉個例子:
let phone = "020-85653333";
let reg = /(0\d{2})-(\d{8})/;
phone.match(reg);
//輸出結果:
[
0: "020-85653333",
1: "020",
2: "85653333",
groups: undefined,
index: 0,
input: "020-85653333"
]
其實分組個數是2,但是因為第0個為整個運算式本身,因此也一起輸出了,
2、 命名編號捕獲組:
語法:(?
解釋:分組的命名由運算式中的name指定,
比如我們電話匹配加上命名:
let phone = "020-85653333";
let reg = /(?<quhao>0\d{2})-(?<num>\d{8})/;
phone.match(reg);
//輸出結果:
[
0: "020-85653333",
1: "020",
2: "85653333",
groups: {quhao: "020", num: "85653333"},
index: 0,
input: "020-85653333"
]
輸出結構可以看到,groups物件會以命名分組存放對應的匹配資料,
3、非捕獲組:
語法:(?:exp)
解釋:和捕獲組剛好相反,它用來標識那些不需要捕獲的分組,說的通俗一點,就是你可以根據需要去保存你的分組,
如果我們不想匹配區號,那我們可以:
let phone = "020-85653333";
let reg = /(?:0\d{2})-(\d{8})/;
phone.match(reg);
//輸出結果:
[
0: "020-85653333",
1: "85653333",
groups: undefined,
index: 0,
input: "020-85653333"
]
3、反向作用
捕獲會回傳一個捕獲組,這個分組是保存在記憶體中,不僅可以在正則運算式外部通程序式進行參考,也可以在正則運算式內部進行參考,這種參考方式就是反向參考,
根據捕獲組的命名規則,反向參考可分為:
1、數字編號組反向參考:\number
2、命名編號組反向參考:\k<name>
概念都是比較模糊,我們直接舉例說明:
我們有串字符'aabbcddddeffg',需要捕獲兩個連續相同的字母,我們需要解決的關鍵在于怎么判斷上下兩個字母是相同,
let str = 'aabbcddddeffg';
let reg = /(\w)\1/g;
str.match(reg); // ["aa", "bb", "dd", "dd", "ff"]
這其中的\1是什么意思呢?其實就是獲取捕獲的第一個分組,下面我們再舉例說明
let reg = /(\d)(\d)\d\1\2/;
let str = 'a12312b';
reg.test(str); //true
// 第一個(\d)捕獲匹配到了1,這時候會存在記憶體,\1=1,
// 第二個(\d)捕獲匹配到了2,\2=2,
// 此時正則運算式的可以解讀成/12\d12/
let str1 = 'a12345b';
reg.test(str1); //false
4、貪婪和非貪婪
1、貪婪匹配:
當正則運算式中包含能接受重復的限定符時,通常的行為是(在使整個運算式能得到匹配的前提下)匹配盡可能多的字符,這匹配方式叫做貪婪匹配,
貪婪匹配是重復限定符( *, +, ?, {n}, {n,}, {n,m} )特有的,
舉個例子:
let phone = "aibicidiei";
let reg = /a\w*i/g;
phone.match(reg); //["aibicidiei"]
"ai"其實已經可以滿足匹配規則,但是在貪婪模式上它并不滿足,而是匹配到了最大能匹配的字符"aibicidiei",
2、懶惰(非貪婪):
有貪婪模式那必然也有非貪婪模式,
特性:當正則運算式中包含能接受重復的限定符時,通常的行為是(在使整個運算式能得到匹配的前提下)匹配盡可能少的字符,這匹配方式叫做懶惰匹配,
懶惰量詞是在貪婪量詞后面加個?,如:
let phone = "aibicidiei";
let reg = /a\w*?i/g;
phone.match(reg); //["ai"]
5、反義
語法:[^]
用得不多,就簡單提及一下,如不想匹配abc這三個字符:
let reg = /[^abc]/
6、replace
上文又提及過replace第二個引數可以是字串或者函式,
字串的時候,它有幾個特定字符,
| 字符 | 描述 |
|---|---|
| $ | 匹配字串左邊的字符 |
| $' | 匹配字串右邊的字符 |
| $& | 與正則相匹配的字串 |
| $i (i:1-99) | 匹配結果中對應的分組匹配結果 |
看著有點抽象,寫個代碼就一目了然了,
'abc'.replace(/b/,"$"); //acc
'abc'.replace(/b/,"$`"); //aac
let str = '正則運算式從入門到入坑';
str.replace(/正則運算式/,'{$&}'); //"{正則運算式}從入門到入坑"
let str2 = 'xyz';
str2.replace(/(x)(y)(z)/,"$3$2$1"); //"zyx"
第二個引數是函式,且正則使用分組捕獲的時,函式會3個引數分別是:
0、匹配到的子字串;
1、匹配到的子串的索引位置;
2、源字串本身;
let str = "This is the flower cat and the civet cat.";
let reg = /cat/g;
str.replace(reg,function(){
console.log(arguments);
return "tiger";
})
// 第一次列印結果:
[
0: "cat",
1: 19,
2: "This is the flower cat and the civet cat."
]
// 第二次列印結果:
[
0: "cat",
1: 37,
2: "This is the flower cat and the civet cat."
]
當正則使用了分組捕獲時,函式引數依次是:
0、匹配到的子字串;
1、第一個分組項(如存在多個分組會按序緊跟回傳);
(總分組項+2)、匹配到的子串的索引位置;
(總分組項+3)、源字串本身;
例:
let str = 'abc';
let reg = /(a)(b)(c)/g;
str.replace(reg,function(){
console.log(arguments);
return arguments[3]+arguments[2]+arguments[1]; //cba
})
// arguments列印結果:
[
0: "abc",
1: "a",
2: "b",
3: "c",
4: 0,
5: "abc"
]
// 等價于
str.replace(reg,"$3$2$1");
完結撒花,寫了這么多其實只是正則的基本語法,只有在實際專案上運用上了才能見識到它的靈活性和博大精深,語法不多,但是用法卻很多,說一句花里胡哨不為過,入坑之后的填坑就全靠自己了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/499667.html
標籤:其他
下一篇:Vue路由
