JavaScript正則表達式一瞥

正則表達式

正則表達式自己是一種匹配模式,用計算機語言來描述咱們須要匹配到的結構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 單詞邊界

\b 是單詞邊界,具體就是 \w 與 \W 之間的位置,也包括 \w 與 ^ 之間的位置,和 \w 與 $ 之間的位置。

var result = "[JS] Lesson_01.mp4".replace(/\b/g, '#');
console.log(result);
// => "[#JS#] #Lesson_01#.#mp4#"
複製代碼

\B 非單詞邊界

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"
複製代碼

(?=p) 先行斷言

好比 (?=l),表示 "l" 字符前面的位置,不包括p模式匹配的字符

var result = "hello".replace(/(?=l)/g, '#');
console.log(result);
// => "he#l#lo"
複製代碼

(?!p) 是 (?=p) 取反 先行否認斷言

除匹配p模式前面之外的位置

var result = "hello".replace(/(?!l)/g, '#');
console.log(result);
// => "#h#ell#o#"
複製代碼

(?<=p) 後行斷言

位置前面要知足匹配p模式,不包括p模式匹配的字符

var result = "hello".replace(/(?<=l)/g, '#');
console.log(result);
// => "hel#l#o"
複製代碼

(?<!p) 後行否認斷言

位置前面要知足匹配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

默認是找到第一個匹配字符就中止,加了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' ]
複製代碼

y

與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

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

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提供了一些操做給咱們使用,下面依次介紹

起始API exec

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/ 可能只是路徑中的一部分,可是咱們須要檢查的量已經能夠接受了。

總結

正則表達式能夠說是很是複雜,本文僅僅是本人閱讀資料以後對正則自己的知識梳理,至於真正去使用,仍是須要多加練習,面對處理文本狀況多,正則就會更多駕輕就熟,本文是在本身面對一個業務須要處理複雜文本狀況下,被正則打擊,因而不服產生的產物,同時也但願你們,在瞭解正則的概貌以後,可以不懼怕正則,能看懂,並寫一些簡單的,最後本人不是大佬喔,只是有時候死腦筋,想學習多一點,哈哈,因此真要問我正則怎麼寫,我只能溜了溜了(劃掉)。

相關文章
相關標籤/搜索