正則表達式自己是一種匹配模式,用計算機語言來描述咱們須要匹配到的結構javascript
正則表達式是一門強大的技術,尤爲在處理文本上很有卓效,本文是在閱讀諸多資料後,寫給本身的正則入門資料,主要參考的老姚的《正則迷你書》,很是感謝大佬的無私奉獻,十分推薦去看原書,本人只是拾人牙慧,另外注入了本身對正則的一些理解,適合入門看css
正則表達式從匹配形式來講,要麼匹配字符,要麼匹配位置,如下從分別這兩點展開學習html
正則匹配到的字符串是不固定的 可使用量詞來指定片斷出現的次數,次數會影響到字符串的長度,由於稱之爲橫向模糊匹配java
示例正則表達式
var regex = /ab{2,5}c/g ; //g 含有一個a,2-5個b,一個c的字符串
var string = "abc abbc abbbc abbbbc abbbbbc abbbbbbc";
console.log( string.match(regex) );
// 匹配結果,只有知足先後位ac,中間只有2-5個b的字符串都匹配到了
// => ["abbc", "abbbc", "abbbbc", "abbbbbc"]
複製代碼
量詞編程
指定須要匹配的字符次數,一些常見的次數表示能夠用等價的符號代替,以下圖:api
表示同一個位置能夠匹配多種可能的字符數組
示例dom
var regex = /a[123]b/g; // 匹配以a開頭,以b結尾,中間含有123任意其中一個的字符
var string = "a0b a1b a2b a3b a4b";
console.log( string.match(regex) );
// 匹配結果
// => ["a1b", "a2b", "a3b"]
複製代碼
字符組ide
常見的縱向模糊匹配集合別名
字符組
1.匹配日期
匹配 2017-06-10
分析
年 四位數字便可 [0-9]{4}
月,分爲0開頭和1開頭 0[1-9] | 1[0-2]
日,分爲0、一、二、3開頭 0[1-9] | [12][1-9] | 3[01]
正則
var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
console.log( regex.test("2017-06-10") );
// => true
複製代碼
2.匹配id
分析
id="." 可是因爲是貪婪匹配,就會匹配到最後一個雙引號爲止
id=".*?" 可使用惰性匹配來解決,可是效率低下
id="[^"]*" 最佳
正則
var regex = /id="[^"]*"/
var string = '<div id="container" class="main"></div>';
console.log(string.match(regex)[0]);
// => id="container"
複製代碼
位置在正則中表示相鄰字符之間的位置,有如下描點
表示字符串開頭,多行字符表示行開頭
表示字符串開頭,多行字符表示行結尾
下面咱們能夠把字符串的開頭和結尾用'#'代替
單行
var result = "hello".replace(/^|$/g, '#');
console.log(result);
// => "#hello#"
複製代碼
多行
var result = "I\nlove\njavascript".replace(/^|$/gm, '#');
console.log(result);
/* #I# #love# #javascript# */
複製代碼
\b 是單詞邊界,具體就是 \w 與 \W 之間的位置,也包括 \w 與 ^ 之間的位置,和 \w 與 $ 之間的位置。
var result = "[JS] Lesson_01.mp4".replace(/\b/g, '#');
console.log(result);
// => "[#JS#] #Lesson_01#.#mp4#"
複製代碼
var result = "[JS] Lesson_01.mp4".replace(/\B/g, '#');
console.log(result);
// => "#[J#S]# L#e#s#s#o#n#_#0#1.m#p#4"
複製代碼
好比 (?=l),表示 "l" 字符前面的位置,不包括p模式匹配的字符
var result = "hello".replace(/(?=l)/g, '#');
console.log(result);
// => "he#l#lo"
複製代碼
除匹配p模式前面之外的位置
var result = "hello".replace(/(?!l)/g, '#');
console.log(result);
// => "#h#ell#o#"
複製代碼
位置前面要知足匹配p模式,不包括p模式匹配的字符
var result = "hello".replace(/(?<=l)/g, '#');
console.log(result);
// => "hel#l#o"
複製代碼
位置前面要知足匹配p模式,除此位置的其他位置
var result = "hello".replace(/(?<=l)/g, '#');
console.log(result);
// => "#h#e#llo#"
複製代碼
1.數字千位分隔符表示法
好比把 "12345678",變成 "12,345,678"。
分析
這個匹配一看就是匹配3位數字的前面的位置,可使用先行斷言來匹配 ?=\d{3}+, 就能夠作到
正則
var result = "12345678".replace(/(?=(\d{3})+$)/g, ',')
console.log(result);
// => "12,345,678"
// 這裏嘗試3的倍數位數字會發現開頭也加上了,
var result = "112345678".replace(/(?=(\d{3})+$)/g, ',')
console.log(result);
// => ",112,345,678"
// 限制此位置不能是開頭便可
var regex = /(?!^)(?=(\d{3})+$)/g;
result = "123456789".replace(regex, ',');
console.log(result);
// => "123,456,789"
複製代碼
1.實現字符串trim方法
分析
trim是用來去除字符串首尾空白符,嗯,一讀這句話,首尾,那不就是首尾 ^ $嘛,至於空白符\s就完事了
正則
function trim(str) {
return str.replace(/^\s+|\s+$/g, '')
}
console.log(trim(' foobar '))
複製代碼
trim方法是實現首尾去除空白符的,那麼新推出的trimStart,trimEnd如何實現呢,嘻嘻,你們能夠想一想
轉義這個問題是指一些符號在正則中擁有特殊的含義,好比^表示字符串起始位置,那麼如何表示^這個字符串呢,那麼就須要特殊的方法,成爲轉義,轉義只須要在字符前加上\
^、$、.、*、+、?、|、\、/、(、)、[、]、{、}、=、!、:、- ,
複製代碼
這裏的匹配行爲由將咱們正則轉爲計算機語言的狀態機決定,常見的有DFA和NFA, 目前比較常見的是NFA,JavaScript也是採用NFA實現的正則引擎,NFA一個特色就是會產生回溯行爲,生動地來將,它採用相似深度優先搜索思想,遍歷可能匹配的字符串,一旦下一次匹配失效,便可回退到前一個狀態,聽起來很像拿着線球走出迷宮的故事,回溯行爲從直觀想就能得知會影響效率,在JavaScript正則中,常見的回溯形式爲貪婪量詞、惰性量詞、分支結構,下面會依次介紹
最大範圍匹配
var regex = /\d{2,5}/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
// => ["123", "1234", "12345", "12345"]
複製代碼
示例中會將數字儘量匹配到,因此看到匹配的數字都是依照空白符分割開來的
最小匹配範圍
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"]
複製代碼
示例中,加了?的量詞以後,正則會在匹配2個數字以後中止
子模式任選其一 屬於惰性匹配具體形式以下:(p1|p2|p3),其中 p一、p2 和 p3 是子模式,用 |(管道符)分隔,表示其中任何之一。
var regex = /good|nice/g;
var string = "good idea, nice try.";
console.log( string.match(regex) );
// => ["good", "nice"]
複製代碼
在不少語言語法中,括號最多見的是表明優先級,咱們看看括號在正則表達式中有什麼特殊的用途呢?
/ab+/ => a接上至少一個b
如何把量詞做用與一個總體
/(ab)+/
複製代碼
表達分支的可能性
表示p1或p2表達式任選其一
var regex = /^I love (JavaScript|Regular Expression)$/;
console.log( regex.test("I love JavaScript") );
console.log( regex.test("I love Regular Expression") );
複製代碼
用於捕獲括號匹配的結果
1.提取數據
括號裏的匹配字符串能夠被直接引用,用以特定的場景
提取年月日
var regex = /(\d{4})-(\d{2})-(\d{2})/g;
var string = "2017-06-12";
console.log( string.match(regex) );
console.log( RegExp.$1 ); // 2017
console.log( RegExp.$2 ); // 06
console.log( RegExp.$3 ); // 12
複製代碼
2.替換
日期更換格式
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, "$2/$3/$1");
console.log(result);
// => "06/12/2017"
複製代碼
能夠引用以前出現的分組結果
有時候須要引用前面匹配的結果,好比說下面要求日期分隔符一致
// 1表示出現的一個分組中的匹配結果
var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // false
複製代碼
不捕獲匹配的結果
var regex = /(?:ab)+/g;
var string = "ababa abbb ababab";
console.log( string.match(regex) );
// => ["abab", "ab", "ababab"]
複製代碼
// 駝峯化
function camelize (str) {
return str.replace(/[-_\s]+(.)?/g, function (match, c) {
return c ? c.toUpperCase() : '';
});
}
console.log( camelize('-moz-transform') );
// 中劃線化
function dasherize (str) {
return str.replace(/([A-Z])/g, '-$1').replace(/[-_\s]+/g, '-').toLowerCase();
}
console.log( dasherize('MozTransform') );
複製代碼
emmmm,雖然有優先級,可是有時候仍是會看不懂,使用可視化工具幫助咱們理解是一個很不錯的idea,這裏有一個正則可視化網址,在閱讀不懂的正則表達式能夠助你一臂之力。
修飾符 | 描述 |
---|---|
g | 全局匹配,即找到全部的匹配 |
y | 全局匹配,即找到全部的匹配,可是此匹配要求每一個子串是連續下標的 |
i | 忽略字母大小寫 |
m | 多行匹配,使用^和$匹配多行時使用 |
默認是找到第一個匹配字符就中止,加了g修飾符就會找到字符串中全部匹配的字符,下面的例子一目瞭然
var regex = /\d/;
var string = "123";
console.log( string.match(regex) ); // [ '1', index: 0, input: '123' ]
var regex = /\d/g;
var string = "123";
console.log( string.match(regex) ); // [ '1', '2', '3' ]
複製代碼
與g行爲一致的是,找到全局匹配的子串,可是y有特殊行爲,要求每個匹配的子串起始位置必須是上一個子串的結束位置,看一下下面的例子
var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;
r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]
r1.exec(s) // ["aa"]
r2.exec(s) // null
複製代碼
i的含義比較簡單
var regex = /\A/i;
var string = "a";
console.log( string.match(regex) ); // [ 'a', index: 0, input: 'a' ]
var regex = /\A/i;
var string = "A";
console.log( string.match(regex) ); // [ 'A', index: 0, input: 'A' ]
複製代碼
m就是爲了讓^和$變成行頭和行尾
var regex = /^A$/g;
var string = "A\nA";
console.log( string.match(regex) ); // null
var regex = /^A$/mg;
var string = "A\nA";
console.log( string.match(regex) ); // [ 'A', 'A' ]
複製代碼
在咱們使用正則表達式匹配以後,JavaScript提供了一些操做給咱們使用,下面依次介紹
exec是正則表達式編程中最基本的API,它擁有對字符串匹配,迭代的能力,後續API能夠理解爲特定場景的exec封裝的API,在默認狀況下返回第一次匹配的字符串,g修飾符下,每次從上一個匹配結果末端下標開始查找下一個匹配結果
/** 方法返回正則匹配的字符串 * @param string 執行的字符串 * @return {RegExpExecArray | null} 正則執行數組或者null */
exec(string: string): RegExpExecArray | null;
複製代碼
let reg = /\d/g;
let s = "123456"
console.log(reg.exec(s)); // [ '1', index: 0, input: '123456' ]
console.log(reg.exec(s)); // [ '2', index: 1, input: '123456' ]
複製代碼
檢測目標字符串是否有知足匹配的子串,注意驗證是有沒有子串,比較經常使用的是test,它直接返回boolean表示驗證結果
/** 方法返回一個布爾值表示是否在給定字符串中,存在一個匹配正則的字符串 * @param string 被測試的字符串 * @return {boolean} 是否匹配存在匹配子串 */
test(string: string): boolean;
複製代碼
能夠理解成一下代碼
// 如有一個或多個匹配,第一次匹配都能匹配到結果,不然返回null
RegExp.prototype.test = function (str) {
return !!this.exec(str)
}
let reg = /\d/;
let s = "123456"
console.log(reg.test(s)); // true
複製代碼
實例
var regex = /\d/;
var string = "abc123";
console.log( regex.test(string) ); // true
複製代碼
在匹配標誌符位置進行切分
/** 方法按照給定的正則返回分割後的子串數組 * @param separator 分割器,可使一個字符串或者一個正則表達式 * @param limit 返回結果數組的長度 * @return {string []} 分割後的字符串數組 */
split(separator: string | RegExp, limit?: number): string[];
複製代碼
能夠理解成如下代碼
String.prototype.split = function(reg, limit) {
let curIndex = -1
// 此時this爲String對象,須要拆包
let str = this.valueOf()
let splitArr = []
// 執行exec方法
while(result = reg.exec(str)) {
let findIndex = result.index
// 分隔符相鄰
const isadjoin = curIndex + 1 === findIndex
// 分隔符之間的字符
let splitMiddleStr = str.substring(curIndex + 1, findIndex)
// 這次分割出來的字符
let splitedStr = isadjoin ? "" : splitMiddleStr
splitArr.push(splitedStr)
curIndex = findIndex
}
return splitArr.slice(limit)
}
複製代碼
實例
var regex = /,/;
var string = "html,css,javascript";
console.log( string.split(regex) );
// => ["html", "css", "javascript"]
var regex = /,/;
var string = "html,css,javascript";
console.log( string.split(regex, 1) );
// => ["html"]
複製代碼
注意點
1.使用正則切分,若正則含有捕獲括號, 結果會帶有正則匹配部分
var string = "html,css,javascript";
console.log( string.split(/(,)/) );
// =>["html", ",", "css", ",", "javascript"]
複製代碼
匹配後提取部分數據,比較經常使用的是match
/** * 方法會以給定的正則去匹配字符串,若匹配成功,則返回匹配數組,不然返回null * @param regexp 字符串或者正則對象 * @return {RegExpMatchArray | null} 匹配結果數組或者null */
match(regexp: string | RegExp): RegExpMatchArray | null;
複製代碼
能夠理解成如下代碼
var string = "2017.06.27";
var regex1 = /\b(\d+)\b/;
var regex2 = /\b(\d+)\b/g;
String.prototype.match = function (reg) {
let str = this.valueOf()
// 是否含有g修飾符
let isGlobal = reg.global
let result = []
let curString = ""
if(isGlobal) {
// 返回所有匹配字符串數組
while(curString = reg.exec(str)) {
result.push(curString[1])
}
}else {
// 返回第一次匹配的字符串數組
result = reg.exec(str)
}
return result
}
console.log(string.match(regex1)); // [ '2017', '2017', index: 0, input: '2017.06.27' ]
console.log(string.match(regex2)); // [ '2017', '06', '27' ]
複製代碼
實例
var regex = /^(\d{4})\D(\d{2})\D(\d{2})$/;
var string = "2017-06-26";
console.log( string.match(regex) );
// =>["2017-06-26", "2017", "06", "26", index: 0, input: "2017-06-26"]
複製代碼
注意點
1.match會把第一個參數的字符串轉成正則
var string = "2017.06.27";
console.log( string.match(".") );
// => ["2", index: 0, input: "2017.06.27"]
//須要修改爲下列形式之一
console.log( string.match("\\.") );
console.log( string.match(/\./) );
// => [".", index: 4, input: "2017.06.27"]
// => [".", index: 4, input: "2017.06.27"]
複製代碼
2.match返回值
var string = "2017.06.27";
var regex1 = /\b(\d+)\b/;
var regex2 = /\b(\d+)\b/g;
console.log( string.match(regex1) );
// => ["2017", "2017", index: 0, input: "2017.06.27"] 不帶g返回第一個匹配的字符串,分組捕獲的內容,總體匹配的第一個下標,輸入的目標字符串
console.log( string.match(regex2) ); // 帶g返回全部匹配的內容
// => ["2017", "06", "27"]
複製代碼
替換匹配的信息進行處理,使用replace
/** * 方法按照匹配規則, 使用替換值,將匹配字符串替換爲替換值 * @param searchValue 須要匹配的字符串或者正則 * @param replaceValue 替換字符串 * @return {string} 替換後的字符串 */
replace(searchValue: string | RegExp, replaceValue: string): string;
/** * 方法按照匹配規則, * @param searchValue 須要匹配的字符串或者正則 * @param replacer 替換器 * @return {string} 替換後的字符串 */
replace(searchValue: string | RegExp, replacer: (substring: string, ...args: any[]) => string): string;
複製代碼
能夠理解成如下代碼,這裏僅實現全局替換,讓你們瞭解replace是一個特定場景下的api便可
var string = "2017.06.27";
var regex2 = /\d+/g;
String.prototype.replace = function (reg, replaceValue) {
// 這裏this是String對象,使用valueOf取出字符串值
let str = this.valueOf()
// 先用正則分割字符串
let strArr = str.split(reg)
// 用replaceValue來填充替換的地方
str = strArr.join(replaceValue)
return str
}
console.log(string.replace(regex2, "1")); // 1.1.1
複製代碼
實例
var string = "2017-06-26";
var today = new Date( string.replace(/-/g, "/") );
console.log( today );
// => Mon Jun 26 2017 00:00:00 GMT+0800 (中國標準時間)
複製代碼
注意點
1.當第二個參數是字符串是,有如下特殊字符
2.當第二個參數是函數時,函數傳入參數
// 從左往右分別是匹配的字符串,捕獲組,匹配字符串的起始位置,輸入字符串
[match, $1, $2, index, input]
let a = "1234 2345 3456".replace(/(\d)\d{2}(\d)/g, function (match, $1, $2, index, input) {
console.log([match, $1, $2, index, input]);
});
// => ["1234", "1", "4", 0, "1234 2345 3456"]
// => ["2345", "2", "5", 5, "1234 2345 3456"]
// => ["3456", "3", "6", 10, "1234 2345 3456"]
複製代碼
3.replace能夠嵌套使用
在一些需求裏,咱們無可避免須要屢次正則處理,好比先找到一個總體,再替換這個總體裏面的一部分,以縮小影響範圍,能夠先用replace匹配一次子串,而後再次縮小範圍匹配
let domStr = `<div style="font-family: Times, serif, 'Times New Roman'" class="333"> <div style="font-family: Verdana, Geneva, Tahoma, sans-serif;color: #333"> <div style="font-family: Verdana, Geneva, Tahoma, sans-serif;color: #333"></div> </div> </div>`
// 匹配style="" 關鍵是中間部分
let styleRegex = /style="[^"]*"/g
let result = domStr.replace(styleRegex, function(style) {
console.log(style);
let isoffFontFamily = /font\-family\:([^;])*(Times New Roman)+([^;"])*/;
return style.replace(isoffFontFamily, "");
})
console.log(result);
// <div style="" class="333">
// <div style="font-family: Verdana, Geneva, Tahoma, sans-serif;color: #333">
// <div style="font-family: Verdana, Geneva, Tahoma, sans-serif;color: #333"></div>
// </div>
// </div>
複製代碼
1.通常不使用構造函數生成正則表達式,優先使用字面量,除非須要動態生成正則表達式
2.修飾符都有其對應的對象布爾屬性表面是否啓用
修飾符 | 實例屬性 |
---|---|
g | global |
y | sticky |
i | ingnoreCase |
m | multiline |
3.能夠用過source實例屬性來查看動態構建的正則表達式結果
var className = "high";
var regex = new RegExp("(^|\\s)" + className + "(\\s|$)");
console.log( regex.source )
// => (^|\s)high(\s|$) 即字符串"(^|\\s)high(\\s|$)"
複製代碼
如何針對問題,構建一個合適的正則表達式
1.是否有必要使用正則
人在學習一個新東西以後,很容易陷入到無所不用,正則亦是如此,其實不少時候,咱們可以用字符串API解決,就不須要正則出手,如下是一些例子
var string = "2017-07-01";
var regex = /^(\d{4})-(\d{2})-(\d{2})/;
console.log( string.match(regex) );
// => ["2017-07-01", "2017", "07", "01", index: 0, input: "2017-07-01"]
var result = string.split("-");
console.log( result );
// => ["2017", "07", "01"]
複製代碼
在年月日的例子中,咱們使用了正則來獲取年月日,相比採用分隔符,增長了代碼的複雜性。
2.是否有必要嚴格匹配
通常來講,正則因爲其複雜性,複雜度上來,須要嚴格匹配就很困難,應該結合場景,夠用就行,或者能夠作一些預處理字符串,使得匹配難度下降。
3.效率
1.儘可能使用具體字符組代替通配符,減小回溯
/.*/ 雖然能夠匹配任意字符,可是由於貪婪性質,容易出現回溯行爲,好比下面的
匹配"abc" 使用/".*"/
// 123"abc"456
複製代碼
在匹配過程當中進行了4次回溯,因爲回溯須要保存以前的狀態,因此須要額外的空間,另外,回溯行爲很直觀地能夠看出來影響效率,/"[^"]*"/,就能夠避免回溯行爲
2.獨立出肯定字符,加快匹配判斷速度
/a+/
// 若是能肯定是好比字符a是存在的,能夠改寫成一下正則,加快匹配判斷速度
/aa+/
複製代碼
3.對多選分支提取公共部分,減小匹配過程當中可消除的重複
/^abc|^def/ //修改爲
/^(?:abc|def)/。
// 多選分支是惰性的,在不匹配分支的時候,會嘗試其餘分支,公共的部分也會進行這個匹配,然而公共的部分沒有必要屢次匹配,能夠提取出來,減小匹配中的重複過程
複製代碼
4.使用非捕獲括號
在咱們不須要捕獲括號中的內容時,可使用非捕獲括號來省掉本來用戶保存捕獲內容的內存
1.快速找出Vue源碼中的全部正則
嘗試一
/\/.*\//
// 這會把註釋、路徑、域名都匹配進去
// /* */
複製代碼
結果
嘗試2
/\/(\S)+\//
// 這裏匹配了非空字符串,並且必須至少出現一次
// /* */
複製代碼
結果
一會兒少了不少,可是仍然會匹配到不是正則的 /ggg/ 可能只是路徑中的一部分,可是咱們須要檢查的量已經能夠接受了。
正則表達式能夠說是很是複雜,本文僅僅是本人閱讀資料以後對正則自己的知識梳理,至於真正去使用,仍是須要多加練習,面對處理文本狀況多,正則就會更多駕輕就熟,本文是在本身面對一個業務須要處理複雜文本狀況下,被正則打擊,因而不服產生的產物,同時也但願你們,在瞭解正則的概貌以後,可以不懼怕正則,能看懂,並寫一些簡單的,最後本人不是大佬喔,只是有時候死腦筋,想學習多一點,哈哈,因此真要問我正則怎麼寫,我只能溜了溜了(劃掉)。