HTML基礎學習 ##前言 HTML基礎學習會由HTML基礎標簽學習、HTML表單學習和一張思維導圖總結HTML基礎三篇文章構成,文章中博主會提取出重點常用的知識和經常出現的bug,提高學習的效率,後續會相繼上傳CSS和JavaScript的博客,歡迎大家一起學習,共同進步。 註:HTML基礎學習的 ...
正則表達式從入門到入坑
入坑前先介紹兩個輔助網站:
正則表達式測試網站: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");
完結撒花,寫了這麼多其實只是正則的基本語法,只有在實際項目上運用上了才能見識到它的靈活性和博大精深,語法不多,但是用法卻很多,說一句花里胡哨不為過,入坑之後的填坑就全靠自己了。