一直以來,或多或少,都能聽到正則表達式的強大之處,可是都沒有怎麼去系統的學習,遇到問題的時候,直接百度一下答案,可是很顯然,這樣子是不行滴,因此最近開始了正則表達式的學習,也收穫了不少,在此作了一些總結,也但願對你們有所幫助。javascript
正則表達式,最核心的也是最基本的元素就是 字符,全部的正則表達式都是由一個個字符組合而成的,因此,咱們首先要弄清楚字符是什麼?有哪些字符?以及因爲字符衆多,怎麼把這些字符有體系的記住?把上面這些理清楚之後,咱們再結合前端實戰中具體的正則表達式案例再進一步鞏固上面的知識點。css
字符分爲轉義字符與非轉義字符,這裏,我我的是把是否轉義做爲一個分類的標準,也就是把 " 左斜槓\ " 做爲一類,叫作轉義字符,其餘字符統一做爲非轉義字符,是由於轉義字符確實是一個比較特殊的存在,它可讓任何字符轉義,包括它本身。html
咱們接下來具體看一下字符的分類:前端
即 左斜槓 \ 這個字符,轉義的意思就是轉變成其餘的意思,因此 左斜槓的做用就是能夠將它後面的字符轉義成其餘的意思,java
注意:它能夠將任何字符轉義,包括它本身。正則表達式
例如:
例1:字母n,單獨使用的時候就是單純的一個字母,可是加上轉義字符以後, \n 就表示換行符
例2:字母r,單獨使用的時候就是單純的一個字母,可是加上轉義字符以後, \r 就表示回車符
例3:字母\,它自己的做用就是轉義字符,可是若是隻是想匹配一個左斜槓呢,此時咱們就須要將它自己進行轉義,即\\, 此時 \\ 表示的是一個單純的 左斜槓字符。
複製代碼
非轉義字符,即除了轉義字符\ 以外的全部字符。它自己能夠普通字符和元字符。後端
咱們平時所用的大小寫字母,數字,標點符號等,沒有特殊含義。數組
例如:字母a,就是一個單純的字母,沒有其餘任何意思,數字也同樣,只是一個數字
複製代碼
又叫特殊字符:即有些字符是有本身的特殊含義,不是一個單純的字符。例如^,$等位置限定符,還有{},* + 等量詞限定符,bash
注意:其實不用單獨去記全部的字符,而是要知道全部字符的一個大致分類,而後以後再根據實際場景去使用相應的字符。post
即一個正則表達式,都是由字面量,字符組,量詞,位置,分組,分支等結構組成。
即只是一個單純的具體字符,首先來一個最最簡單的正則表達式:
let reg = /abc/;
let str = 'abc';
reg.test(str); // true
複製代碼
即咱們匹配的只是一個固定字符,而後整個字符精確匹配便可,一般這種狀況下,不須要咱們使用正則,直接使用indexOf,或者includes方法便可解決,
那若是咱們要匹配的不是一個固定的字符,若是要同時匹配多個字符呢?答案是 字符組
例如:咱們判斷:字符串是否包含a或者b或者c
let reg = /[abc]/ // [abc] 表示匹配a或者b或者c
let str1 = 'a';
let str2 = 'b';
let str3 = 'c';
let str4 = 'abc';
reg.test(str1);//true
reg.test(str2);//true
reg.test(str3);//true
reg.test(str4);//true
複製代碼
固然,若是咱們要匹配的字符特別多,咱們也可使用範圍表示法去匹配
例如:咱們要判斷:字符串是否包含a到z中的任意一個字符,這時候,若是咱們還使用[abc...z] 這樣列出全部字符就有點太麻煩了,解決方式 「橫槓-」
- [a-z] //表示a-z中的任意一個小寫字符
- [A-Z] //表示A-Z中的任意一個大寫寫字符
- [0-9] // 表示0到9的人一個數字
例如:咱們要判斷:一個字符串是否包含 數字,大小寫字母以及下環線
let reg = /a-zA-Z0-9_/;
let str = '123';
reg.test(str);// true
複製代碼
上面的例子是否還有優化的地方呢?有的,那就是/a-zA-Z0-9_/屬於正則表達式當中比較經常使用的表達式,因此,正則表達式針對那些比較經常使用的表達式,又進一步作了簡寫,這樣更方便使用。
例如:/a-zA-Z0-9_/ 就能夠用/\w/ 替換,下面還有一些其餘常見簡寫:
經過字符這一節,咱們須要知道,字符組的使用場景:即若是想要匹配多個字符的時候,就可使用[],方括號裏面的字符是一個或的關係。
還有一個場景:排除字符組,
即咱們想要匹配的能夠是任意字符,可是就是不能包含a,b,c。
/[^abc]/ 即該正則的含義就是咱們想要匹配的字符串不能包含a或b或c。
複製代碼
上面的字符組是對字符的內容進行限定,而量詞是對字符的數量進行限定,限定這個字符串的長度,或者某個字符的重複次數等等,都會使用的量詞, 首先,咱們來看一下怎麼表示量詞?
符號 | 功能 | 記憶方法 |
---|---|---|
* | 0次或者屢次 | 通常 * 就表示的是全部嘛,即0次或者屢次 |
+ | 1次或者屢次 | 知道 1+ 這個手機牌名稱就記住了 |
? | 0次或者1次 | 問號通常表示有沒有或者是或否,因此用0和1表示 |
{n} | 指定n次 | |
{n,} | 至少n次 | |
{n, m} | 至少n次,最多m次 | |
{, m} | 最多m次 |
接下來,咱們結合幾個例子看一下:
例子1: 判斷輸入的手機號是否符合要求?(這裏咱們先簡單的限定手機號的長度便可)
let reg = /\d{11}/;
let phone = '13241444699';
reg.test(phone); // true
例子2: 判斷用戶名:只能是數字字母和下劃線,且6到12位
let reg = /\w{6,12}/;
let username = 'admin';
reg.test(username); //false ,由於admin只有5位
例子3: 匹配連續出現2位到55位的數字。
var regex = /\d{2,5}/;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
// => ["123", index: 0, input: "123 1234 12345 123456", groups: undefined]
若是變成全局匹配呢?
var regex = /\d{2,5}/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
//["123", "1234", "12345", "12345"]
說明:這裏的g是global的意思,是正則表達式其中的一個修飾符(後面會具體介紹修飾符)表示是否全局匹配,若是不是,則匹配到第一個之後,就不會再繼續匹配。
複製代碼
經過上面的例子發現,正則 /\d{2,5}/,表示數字連續出現 2 到 5 次。會匹配 2 位、3 位、4 位、5 位連續數字。可是其是貪婪的,它會盡量多的匹配。你能給我 6 個,我就要 5 個。你能給我 3 個,我就要 3 個。 反正只要在能力範圍內,越多越好。這種狀況能夠叫作 「貪婪匹配」,而與之對應的還有一個「惰性匹配」
那麼?如何實現惰性匹配呢?只要在量詞後面加一個?便可
var regex = /\d{2,5}?/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
//(["12", "12", "34", "12", "34", "12", "34", "56"]
複製代碼
因此,經過上面的例子,咱們也能夠把量詞分爲貪婪量詞和惰性量詞,區別就是惰性量詞就是在貪婪量詞的後面加了個問號?
即採用管道符 | 去實現,其實至關於一個或的概念,
let reg = /html|css|javascript/;
let str = 'html';
reg.test(str); // true 表示只要字符串中包含html或者css或者javascript其中一個都會返回true
複製代碼
首先,我來看一下什麼是位置?
圖中箭頭所知的就是一個個的位置,你也能夠理解位一個個的空字符串。
因此一個完整的表達式,不只僅是有咱們一般意義上所理解的a,b,c等字符,還包含位置字符,兩個合做使用才能夠構成一個完整的表達式。
下面是常見6中位置字符:
符號 | 功能 |
---|---|
^ | 以什麼開頭 |
$ | 以什麼結尾 |
\b | 匹配一個單詞邊界,即字與空格的位置 |
\B | 非單詞邊界匹配 |
(?=p) | 字符p前面的位置,或者當前位置後面必須是字符p |
(?!p) | 與(?=p)相反 |
例如1:
let reg = /^hello/;
let str = 'helloword';
reg.test(str) // true 表示字符串是以hello開頭的
固然也能夠作替換,把開頭和結尾替換成本身想要的字符串
例如2:
let reg = /^|$/g;
let str = 'helloword';
str.replace(reg, '#') // #helloword#
針對於多行字符串的處理:
let reg = /^|$/g;
let str = 'helloword\nhelloword\nhelloword\nhelloword\nhelloword';
str.replace(reg, '#');
//結果爲:
#helloword
helloword
helloword
helloword
helloword#
若是咱們加上m多行修飾符呢?
let reg = /^|$/gm;
let str = 'helloword\nhelloword\nhelloword\nhelloword\nhelloword';
str.replace(reg, '#');
//結果爲:
#helloword#
#helloword#
#helloword#
#helloword#
#helloword#
複製代碼
\b是指\w與\W 之間的位置,也包括 \w 與 ^ 之間的位置,和 \w 與 $ 之間的位置 \B是\b的反面,即除了\b匹配到的位置,剩餘的位置
例如:
let str = '[JS] Lesson_01.mp4';
str.replace(/\b/g, '#'); //[#JS#] #Lesson_01#.#mp4#
str.replace(/\B/g, '#'); //#[J#S]# L#e#s#s#o#n#_#0#1.m#p#4
複製代碼
(?=p) 表示字母p前面的位置,或者當前位置後面的字符是p (?!p) 與(?=p) 相反,即除了(?=p)匹配的位置,剩餘的位置
let str = 'helloworld';
console.log(str.replace(/(?=l)/g, '#'));//he#l#lowor#ld
console.log(str.replace(/(?!l)/g, '#'));//#h#ell#o#w#o#rl#d#
複製代碼
首先說明一下,經過加括號的方式就能夠實現分組,一個括號內的表達式就是一個子表達式,產生組之後,咱們就能夠引用這些組,而不須要再重複書寫。
即咱們書寫正則表達式,生成的組,咱們能夠在字符串中引用。
例如:yyyy-mm-dd要變成 yyyy/mm/dd的格式
let date = '2018-03-12';
方式1: 可能咱們最直接想到的方法是使用replace把-替換成/
date.replace(/-/g, '/');
方式2: 採用分組引用的方式
date.replace(/(\d{4})-(\d{2})-(\d{2})/g, '$1/$2/$3')
複製代碼
生成的分組除了在字符串中引用,咱們還能夠在正則表達式中引用,且只能引用前面的分組。
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
let reg = /\d{4}-|\/|\.\d{2}-|\/|\.\d{2}/
console.log(reg.test(string1));//true
console.log(reg.test(string2));//true
console.log(reg.test(string3));//true
console.log(reg.test(string4));//true
以上方法只是在每一個位置都把- 或者/或者. 這三種狀況用分支選擇列出來,這樣能夠知足咱們目前的需求,可是若是要求先後鏈接符必須一致呢?
let reg = /\d{4}(-|\/|\.)\d{2}\1\d{2}/
console.log(reg.test(string1));//true
console.log(reg.test(string2));//true
console.log(reg.test(string3));//true
console.log(reg.test(string4));//false
此時,咱們能夠看到string4就沒有匹配成功
複製代碼
注意1:若是引用了一個不存在的分組,那正則會按引用字面的意思去處理
例如:
let reg = /(\d{3})\1/
let str = 123123;
console.log(reg.test(str)); // true
若是把分組去掉
let reg = /\d{3}\1/; 即此時\1表示的是一個字符\1,而不會在是分組引用的意思
let str = '123\1'
console.log(reg.test(str));
複製代碼
注意2:若是分組後面加量詞,則引用會表示最後一次匹配到的結果
let str = '12345';
let reg1 = /\d+/;
let reg2 = /(\d)+/;
str.match(reg1);//["12345", index: 0, input: "12345"]
str.match(reg2);//["12345", "5", index: 0, input: "12345"]
複製代碼
經過前面,咱們知道分組是經過小括號生成的,因此說,小括號同時有兩個做用:
若是咱們添加小括號,只是但願它發揮第二種做用,不但願它生成分組,換句話說就是讓當前這個分組變成非捕獲性的,方法是:(?:p)
var string = "ababa abbb ababab";
var regex1 = /(ab)+/g;
//該正則只是但願匹配一個或多個字符'ab',並無但願把它變成一個分組,因此咱們就能夠這個分組變成非捕獲性的。
var regex2 = /(?:ab)+/g
console.log(string.match(regex1)); //["abab", "ab", "ababab"]
console.log(string.match(regex2)); //["abab", "ab", "ababab"]
複製代碼
注意:咱們從例子中看到,把它變成非捕獲性分組之後,匹配的結果依然是同樣的,那非捕獲性分組有什麼用呢?
提升匹配性能,由於捕獲性分組和反向引用在匹配的時候,都會把匹配到結果存到內存中,這樣咱們在引用的時候,才能夠引用匹配到的值,可是非捕獲性分組,這一步就省略了,因此匹配速度會更快。
經過上面對正則表達式各類結構的介紹,咱們知道了正則表達式的結構或者由哪些字符組成,接下來,咱們只需肯定這些結構的優先級,而後一步步按照優先級從高到低拆分便可。
首先,咱們看一下正則表達式有哪些結構組成?
整體來講:轉義字符\的優先級是最高的,而後就是你們一般意識中的括號(包括小括號和中括號),而後是中括號(即量詞限定符),而後就是位置限定符,最後是用於分支的管道符|,就按照以上解釋的順序去記便可。
注意點:
例如:/\d{4}+/ 若是直接這樣是會報錯的,必須改爲/(\d{4})+/
複製代碼
1. 對字符組[]轉義: /\[34\]/ 等價於 [\[34]]
2. 對量詞限定符號轉義:/\{1,\3}/ 等價於 [\{1,3}]
3. 對轉義字符自己轉義: /\\/
例如:
let str = '[123]';
/\[123]/.test(str); //true
/\[123\]/.test(str); // true
複製代碼
以上咱們說的都是在正則表達式中對字符進行轉義,咱們也能夠在字符串中對字符進行轉義:
let str1 = '^$';
let str2 = '\^\$';
console.log(str1 === str2); //true
複製代碼
首先說一下在js使用正則表達式,最經常使用的6個方法
- test:用於驗證和提取
- exec:用於驗證和提取
- match:用於驗證和提取
- search:用於驗證和提取
- replace:用於替換
- split:用於拆分
即咱們常常遇到的表單驗證,用戶驗證輸入的字符是否符合咱們要求的格式。 經常使用的有四個方法:test與exec屬於RegExp對象的方法,match和search屬於String對象的方法
- reg.test(str); // 返回true或者false
- reg.exec(str); //返回第一個匹配的值
- str.match(reg); //返回第一個匹配的值
- str.search(reg); //返回第一個匹配的值的索引
例如:用戶名驗證,只能包含數字,字母和下劃線
var username = 'abc123';
var reg = /\w/;
reg.test(username); // 結果爲true
reg.exec(username); // 結果爲a
username.match(reg); //結果爲a
username.search(reg); //結果爲0
複製代碼
常常配合正則進行拆分的方式split方法,平時咱們能夠只是傳入一個固定的字符,其實也能夠傳入正則,這樣能夠實現更復雜的場景下的拆分。
例如1:
var str = 'aaa,bbb,ccc';
str.split(',');// [aaa, bbb, ccc]
例如2:
var str1 = 2017/06/26;
var str2 = 2017.06.26;
var str3 = 2017-06-26;
此時,把年月日拆分紅一個數組,最直接的方法就是分別指定拆分符號
str1.split('/');
str1.split('.');
str1.split('-');
可是若是咱們使用正則呢?
var reg = /\D/;
str1.split(reg);
str1.split(reg);
str1.split(reg);
這樣咱們使用一個正則,就能夠拆分多個字符串,而不用分別指定一個固定的字符。
複製代碼
即從字符串中替換出本身想要的信息,例如,一個日期2018-03-12,想要提取出它的年月日分別是什麼。最經常使用的就是match方法
例如:
var regex = /^(\d{4})\D(\d{2})\D(\d{2})$/;
var string = "2017-06-26";
console.log( string.match(regex) );
複製代碼
結果以下:
var regex = /^(\d{4})\D(\d{2})\D(\d{2})$/;
var string = "2017-06-26";
regex.test(string);
console.log( RegExp.$1, RegExp.$2, RegExp.$3 ); //"2017" "06" "26"
複製代碼
固然,也可使用search, exec等方法實現,你們能夠自行試一下
替換指定字符的場景在前端中應用是比較普遍的,例如,咱們的日期顯示的時候須要格式化,從後端獲取到的一段文本,須要對指定單詞進行高亮顯示,這些場景均可以使用正則表達式來替換完成。
例如1:日期格式化
var date = '2018-04-21';
date.replace(/-/g, '/'); // 結果爲 2018/04/21
複製代碼
例如2: 一個段落對指定文本替換成標籤,而且高亮顯示
var text = '你見過凌晨四點的洛杉磯嗎?--- 科比';
接下來,要將 「科比」 替換成指定標籤
text.replace('科比', '<span style="color:red">科比</span>')
結果以下:
複製代碼
例如3: 將如下字符串的大寫字母高亮顯示
var text = 'aaaAAAaaAABBbbCC';
str.replace(/([A-Z])/g, "<span style='color:red'>$1</span>")
複製代碼
注意:若是動態替換某個變量或者不是替換一個固定的值,這種狀況一般都會用到 ‘分組引用’; 也就是下括號 + $
let str = 'index.js';
str.search('.'); // 0 ,由於會自動轉換成正則,在正則中.表示除\n意外的全部字符,因此結果爲0
//因此這時候須要轉義如下字符串,或者直接使用正則
str.search("\\."); // 5
str.search(/\./); //5
複製代碼
//即若是使用g是全局匹配,可能會匹配到多個,若是不是g,那隻會匹配到第一個
let str = 'ababab';
console.log(str.match(/a/)); // ["a", index: 0, input: "ababab"]
console.log(str.match(/a/g)); // ["a", "a", "a"]
注意點:從上面的例子,咱們能夠看到,若是不使用g全局匹配,能夠獲取到匹配值的索引,可是若是使用g,則沒有索引了,解決方法是下面第三點,exec
複製代碼
var regex = /a/g;
console.log( regex.test("a"), regex.lastIndex );
console.log( regex.test("aba"), regex.lastIndex );
console.log( regex.test("ababc"), regex.lastIndex );
// => true 1
// => true 3
// => false 0
複製代碼
console.log( /123/.test("a123b") ); // => true
console.log( /^123$/.test("a123b") ); // => false
console.log( /^123$/.test("123") ); // => true
複製代碼
var string = "html,css,javascript";
console.log( string.split(/,/, 2) );// =>["html", "css"]
var string = "html,css,javascript";
console.log( string.split(/(,)/) );// =>["html", ",", "css", ",", "javascript"]
複製代碼
經過本節內容的介紹,但願看太小夥伴兒能夠對正則表達式有了必定的入門體驗:在前端開發過程當中,正則表達式能夠用於:驗證,拆分,提取,替換等四個經常使用的場景,因此在實際開發過程當中,遇到這些場景,咱們必定思考一下,是否能夠用正則表達式去解決,這樣纔會真正理解正則表達式的強大之處。
在學習過程當中,參考了不少大神的文章,你們也能夠直接學習這些文章
juejin.im/post/5cdcd4… juejin.im/post/596594… juejin.im/post/5b96a8…