【前端js】算法全歸納(二)字符串:JS正則表達式(實用全歸納)


正則表達式

正則表達式是匹配模式,要麼匹配字符,要麼匹配位置
正則表達式工具,常見簡寫形式:https://c.runoob.com/front-end/854


一、正則表達式的六種操作

RegExp 對象方法

1. 驗證test

驗證是否匹配成功,所謂匹配,就是看目標字符串裏是否有滿足匹配的子串,如果有位置符號限定,則去固定位置尋找子串(比如^p$,尋找的就是整個字符串是否匹配)。是正則對象的方法
返回值:
如果字符串 string 中含有與 RegExpObject 匹配的文本,則返回 true,否則返回 false。驗證是正則表達式最直接的應用,比如表單驗證。

RegExpObject.test(string)

var regex = /^(\d{4})-(\d{2})-(\d{2})$/; 
var string = "2017-06-12"; 
regex.test(string) //true
2. 檢索匹配和捕獲的子表達式exec()
RegExpObject.exec(string)

var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12"; 
regex.exec(string)
// ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12", groups: undefined]
//0個元素爲匹配字符串,1——n爲括號子表達式,還有index和input屬性

返回值:
返回一個數組,其中存放匹配的結果。如果未找到匹配,則返回值爲 null。
返回一個數組對象,

  • 此數組的第 0 個元素是與正則表達式相匹配的字符串
  • 第 1 ~n個元素是與 RegExpObject 捕獲的第 1 ~n個子表達式(如果有的話)還含有兩個對象屬性。
  • index 屬性聲明的是匹配文本的起始字符在 stringObject 中的位置,
  • input 屬性聲明的是對 stringObject 的引用。

String 對象的正則方法

1. 第一個匹配索引search()

search() 方法用於檢索字符串中指定的子字符串,或正則表達式相匹配的子字符串位置

stringObject.search(regexp)

var regex = /(\d{2})-/; 
var string = "2017-06-12"; 
string.search(regex)//2 第一個匹配是‘17‘,索引爲2

返回值:
stringObject 中第一個與 regexp 相匹配的子串的索引。search() 方法不執行全局匹配,它將忽略標誌 g

2. 檢索匹配和捕獲的子表達式match()

match() 方法用於檢索字符串中指定的子字符串,或正則表達式相匹配的子字符串以及子表達式

stringObject.match(searchvalue)
stringObject.match(regexp)

//沒有g,匹配一次,獲取子表達式,返回數組對象
var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12"; 
string.match(regex)
//["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12", groups: undefined]

//有g,全局匹配,忽略子表達式,返回純數組
var regex = /(\d{4})-(\d{2})-(\d{2})/g; 
var string = "2017-06-12"; 
string.match(regex)
//["2017-06-12"]

返回值:
存放匹配結果的數組。該數組的內容依賴於 regexp 是否具有全局標誌 g

  • 如果 regexp 沒有標誌 g,那麼 match() 方法就只能在 stringObject 中執行一次匹配。返回一個數組對象,和exec方法返回一樣:
    在這裏插入圖片描述
  • 如果regexp 有標誌 g, match() 方法將執行全局檢索,忽略括號的子表達式,返回一個數組,數組元素中存放的是 stringObject 中所有的匹配子串,而且也沒有 index 屬性或 input 屬性。如果您需要這些全局檢索的信息,可以使RegExp.exec()
    在這裏插入圖片描述
3. 替換字符/遍歷所有匹配replace()

用於在字符串中用一些字符替換另一些字符,或替換一個與正則表達式匹配的子串。

stringObject.replace(regexp/substr,replacement)

replacement 必需。規定了替換文本生成替換文本的函數(可以無返回)

  1. replacement是字符串:
  • 是普通字符串:直接返回替換後的string【全局/非全局】
  • $ 字符,則返回新的模板字符串,$從模式匹配得到的字符串將用於模版。
    • 【常用於非全局匹配,而且知道要操作的字符是哪幾個捕獲的子表達式,返回值可以直接由模版生成,不需要js操作
      在這裏插入圖片描述
      比如,想把 yyyy-mm-dd 格式,替換成 mm/dd/yyyy 怎麼做?
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"

"$2/$3/$1"就是新的模板。

  1. replacement是回調函數:/g全局搜索時每個匹配都調用該函數(相當於一個遍歷),它返回的字符串將作爲替換文本使用。
    • 【常用於全局匹配,而且需要對每一次匹配和子表達式進行操作,而且返回值要經過js操作
    • 該函數的第一個參數是匹配模式的字符串。
    • 接下來的參數是與模式中的子表達式匹配的字符串,可以有 0 個或多個這樣的參數。
    • 接下來的參數是一個整數,聲明瞭匹配在 stringObject 中出現的位置。
    • 最後一個參數是 stringObject 本身。
//函數語法 
function($&,$1,$2……$n,index,string){}

//通過正則表達式獲取 多個url 參數
function getUrlParam(sUrl, sKey) {
  result = {};//用來存儲參數鍵值對
  sUrl.replace(/\??(\w+)=(\w+)&?/g, function(str, key, value) {//對每一次的匹配遍歷這個函數
   if (result[key] !== undefined) {//鍵值已定義
     var t = result[key];
      result[key] = [].concat(t, value);//把新元素拼接成一個數組
    } else {//鍵值未定義
      result[key] = value;//直接爲對象創建這個新屬性
    }
  });
}

如果回調函數沒有返回值,就是沒有定義要返回的字符串,那麼返回的string不會被替換,是原始值。
該例子詳見:https://blog.csdn.net/weixin_28900307/article/details/88193973

4. 分割成字符串數組split()

split() 方法用於把一個字符串分割成字符串數組。

stringObject.split(separator,howmany)//separator	必需。字符串或正則表達式,從該參數指定的地方分割 stringObject。
//howmany	可選。該參數可指定返回的數組的最大長度。

返回值:

  • separator 是不包含子表達式的正則表達式:一個字符串數組。該數組是通過在 separator 指定的邊界處將字符串 stringObject 分割成子串創建的。返回的數組中的字串不包括 separator 自身。
  • 但是,如果 separator 是包含子表達式的正則表達式:那麼返回的數組中包括與由匹配字符串分隔的子串,以及這些子表達式匹配的字串(但不包括與整個正則表達式匹配的文本)。
var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12"; 
string.split(regex)
//["", "2017", "06", "12", ""] 分隔子串+子表達式

二、正則表達式字符匹配

1. 兩種模糊匹配

如果正則只有精確匹配是沒多大意義的,正則表達式之所以強大,是因爲其能實現模糊匹配。

1.1 橫向模糊匹配

量詞
即一個正則可匹配的字符串長度不固定,可以是多種情況。其實現的方式是使用量詞。譬如 {m,n},表示連續出現最少 m 次,最多 n 次。
在這裏插入圖片描述

let r = /ab{2,5}c/g;
let s = "abc abbc abbbc abbbbbbc";
s.match(r); // ["abbc", "abbbc"]
1.1.1 總結:常用橫向匹配的量詞

普通量詞
?等價於 {0,1},表示出現或者不出現。問號的意思表示,有嗎?
* 等價於 {0,},表示出現至少0次。

+ 等價於 {1,},表示出現至少一次。加號是追加的意思。
{n} 表示出現 n 次。
{n,}表示至少出現 m 次。
{n,m} 表示連續出現最少 m~ n 次。

貪婪匹配與惰性匹配量詞
貪婪匹配
普通量詞都是貪婪的,比如 b{1,3},因爲其是貪婪的,嘗試可能的順序是從多往少的方向去嘗 試。首先會嘗試 “bbb”,不能匹配時,再繼續嘗試bb。

本質上就是深度優先搜索算法。其中退到之前的某一步這一過程,我們稱爲“回溯”

如果當多個貪婪量詞挨着存在,並相互有衝突時,前面的部分貪婪,後面的部分貪婪》回溯

var string = "12345"; 
var regex = /(\d{1,3})(\d{1,3})/;
console.log( string.match(regex) ); 
// => ["12345", "123", "45", index: 0, input: "12345"]

其中,前面的 \d{1,3} 匹配的是 “123”,後面的 \d{1,3} 匹配的是 “45”。

惰性匹配
? 量詞後面加個問號就能實現惰性匹配,在匹配成功的前提下,儘可能少的匹配所搜索的字符串。

var string = "123456"; 
var regex = /(\d{1,3}?)(\d{1,3})/; 
console.log( string.match(regex) ); 
// => ["1234", "1", "234", index: 0, input: "12345"]

1.2 縱向模糊匹配

即一個正則可匹配某個不確定的字符,可以有多種可能。如 /[abc]/ 表示匹配 “a”, “b”, “c” 中任意一個。[123]表示匹配1,2,3任意一個。
在這裏插入圖片描述

let r = /a[123]b/g;
let s = "a0b a1b a4b";
s.match(r); // ["a1b"]
1.2.1 字符組(縱向模糊匹配的擴展)

雖叫字符組(字符類),但只是其中一個字符。例如 [abc],表示匹配一個字符,它可以是 “a”、“b”、“c” 之一

範圍表示法

可以指定字符範圍,比如 [1234abcdUVWXYZ] 就可以表示成 [1-4a-dU-Z] ,使用 - 來進行縮寫。

排除字符組

即需要排除某些字符時使用,通過在字符組第一個使用 ^ 來表示取反,如 [^abc] 就表示匹配除了 "a", "b", "c" 的任意一個字符。

1.2.2 多選分支

如我用 /good|goodbye/,去匹配 “goodbye” 字符串時,結果是 “good”:分支結構也是惰性的,即當前面的匹配上了,後面的就不再嘗試了。優先匹配第一個
注意分支h後面的符號會跟隨分支,而不是整個表達式,如果需要請把分支括起來

1.2.3 總結: 常用縱向匹配的表達式

x|y 匹配 x 或 y。xy可以是字符也可以是位置。一般搭配括號使用,所以要注意是否需要用(?:pattern)這種非捕獲括號。例如,‘z|food’ 能匹配 “z” 或 “food”。’(z|f)ood’ 則匹配 “zood” 或 “food”。
[xyz] [^xyz]字符集合。匹配所包含的任意一個字符。匹配未包含的任意字符。
[a-z] [^a-z]字符範圍。匹配指定範圍內的任意字符。匹配任何不在指定範圍內的任意字符。
\d匹配一個數字字符。等價於 [0-9]。
\w匹配字母、數字、下劃線。等價於'[A-Za-z0-9_]'。無特殊符號
\W匹配非字母、數字、下劃線,也就是一些特殊字符,空格,換行。等價於 '[^A-Za-z0-9_]'。有特殊符號
.通配符,匹配除換行符(\n、\r)之外的任何單個字符,有特殊符號。
\n匹配一個換行符。等價於 \x0a 和 \cJ。
\s匹配任何空白字符,包括空格、製表符、換頁符等等。等價於 [ \f\n\r\t\v]
^、$、.、*、+、?、|、\、/、(、)、[、]、{、}、=、!、:、- 屬於元字符,需要加\轉義符轉義


三、正則表達式位置匹配

位置(錨)是相鄰字符之間的位置。比如,下圖中箭頭所指的地方:
在這裏插入圖片描述
所有的位置,我們可以理解成空字符 “”,把 /^helloKaTeX parse error: Expected group after '^' at position 7: / 寫成 /^̲^hello$$/,是沒有任何問題的:

var result = /^^hello$$$/.test("hello"); 
console.log(result); // => true

在 ES5 中,共有 6 個錨:^、$、\b、\B、(?=p)、(?!p)
1.^(脫字符)匹配開頭,在多行匹配中匹配行開頭。
2.$(美元符號)匹配結尾,在多行匹配中匹配行結尾。
多行匹配模式(即有修飾符 m)時,二者是行的概念

var result = "I\nlove\njavascript".replace(/^|$/gm, '#'); 
console.log(result); 
/* #I# #love# #javascript# */

3.\b 是單詞邊界,具體就是 \w 與 \W (特殊符號,空格 )之間的位置,也包括 \w 與 ^字符串以單詞開頭位置) 之間的位置,和 \w 與 $字符串以單詞結尾結尾)之間的位置。
\B 就是 \b 的反面的意思,非單詞邊界。

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"

4.(?=p)正向肯定預查(look ahead positive assert),它代表着:

  1. p 前面的位置,可以定位到這個位置,用replace方法去插入指定字符;
  2. 同樣也可以是一個條件在該位置後面必須有p也就是給後面要匹配的字符串,加上了括號裏面的條件:要匹配的字符串裏面要包含p,具體位置看具體寫法
    (?!p) 正向否定預查(negative assert),就是 (?=p)反面意思
var result = "hello".replace(/(?=l)/g, '#'); 
console.log(result); 
// => "he#l#lo"
var result = "hello".replace(/(?!l)/g, '#'); console.log(result); // => "#h#ell#o#"

5.(?<=p)p 反向(look behind)肯定預查,它代表着:

  1. p 後面的位置,可以定位到這個位置,用replace方法去插入指定字符;
  2. 同樣也可以是一個條件在該位置前面必須有p
    (?<!p)反向否定預查,就是 (?<=p)反面意思
var result = "hello".replace(/(?<=l)/g, '#'); 
console.log(result); 
// => "hel#l#o"
var result = "hello".replace(/(?<!l)/g, '#'); console.log(result); 
// => "#h#e#llo#"

注意:所有預查必須帶括號


四、括號分組引用

4.1 括號捕獲子表達式

在匹配過程中,給每一個**(分組)都開闢一個空間,用來存儲每一個分組匹配到的 數據。捕獲到的數據和匹配的表達式是不一樣的。匹配的字符串是match的首元素,捕獲到的數據是match剩下的元素,或者通過replace的$1234獲取,
分組後面有
量詞**,分組最終捕獲到的數據$1是最後一次的匹配。/(\d)+/匹配"12345"捕獲得到5

"12345abc"匹配/(\d)+/得到12345(有追加,貪心匹配最長),捕獲得到最後一個內容(最後一個追加)5
"12345abc"匹配/(\d)/得到1(無追加,無全局g,匹配第一個),捕獲得到第一個內容1
在這裏插入圖片描述
用法一:match提取多組子串
分組結合match方法常用來從一個字符串中,提取多組子串,比如提取年、月、日

var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12"; console.log( string.match(regex) );
 // => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]

同時,regex是一個RegExp的實例,所以也可以使用RegExp構造函數的全局屬性 $1 至 $9 來獲取:

var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12";
regex.test(string); // 正則操作即可,例如 //regex.exec(string); //string.match(regex);
console.log(RegExp.$1); // "2017" 
console.log(RegExp.$2); // "06" 
console.log(RegExp.$3); // "12"

用法二:repalce操作替換子串
想把 yyyy-mm-dd 格式,替換成 mm/dd/yyyy:

//repalce第二個參數是一個函數,匹配字符串作爲參數傳進去
var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12"; 
var result = string.replace(regex, function (match, year, month, day) { 
return month + "/" + day + "/" + year; 
}); 
console.log(result); // => "06/12/2017"
//repalce第二個參數是一個函數,匹配字符串通過正則構造函數獲取
var regex = /(\d{4})-(\d{2})-(\d{2})/; 
var string = "2017-06-12"; 
var result = string.replace(regex, function () { 
return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1;
 }); 
console.log(result); // => "06/12/2017"

最方便的方法:

//repalce第二個參數是一個字符串
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"

4.2 反向引用

引用之前出現的分組
\num在正則本身裏引用之前出現的分組,比如要寫一個正則支持匹配如下三種格式:
2016-06-12 2016/06/12 2016.06.12
但是又不想匹配 “2016-06/12” 這樣的數據。

var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/; 

保證了兩次的連接符號是一致的
反向引用的注意點

  • \10 表示什麼呢?\10 是表示第 10 個分組,
    如果真要匹配 \1 和 0 的話,請使用 (?:\1)0 或者 \1(?:0)
var regex = /(1)(2)(3)(4)(5)(6)(7)(8)(9)(#) \10+/;
 var string = "123456789# ######" 
 console.log( regex.test(string) ); // => true
  • 括號嵌套怎麼辦?以左括號(開括號)爲準。

4.3 非捕獲括號

(?:pattern)匹配表達式裏面只是想要單純的用括號,匹配 pattern 但不獲取匹配結果,也就是說這是一個非獲取匹配,不進行存儲供以後使用,不需要被反向引用。這在使用 "或字符 (|) 來組合一個模式的各個部分是很有用。例如,
'industr(?:y|ies) 就是一個比 'industry|industries' 更簡略的表達式。
一般來說,別的括號沒有用到捕獲分組的時候,不需要加?:來限定非捕獲。

參考 http://www.w3school.com.cn/jsref/jsref_obj_regexp.asp
https://zhuanlan.zhihu.com/p/59469237

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章