正則表達式
正則表達式是匹配模式,要麼匹配字符,要麼匹配位置
正則表達式工具,常見簡寫形式: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
必需。規定了替換文本或生成替換文本的函數(可以無返回)。
replacement
是字符串:
- 是普通字符串:直接返回替換後的string【全局/非全局】
- 有
$ 字符
,則返回新的模板字符串,$
從模式匹配得到的字符串將用於模版。- 【常用於非全局匹配,而且知道要操作的字符是哪幾個捕獲的子表達式,返回值可以直接由模版生成,不需要js操作】
比如,想把 yyyy-mm-dd 格式,替換成 mm/dd/yyyy 怎麼做?
- 【常用於非全局匹配,而且知道要操作的字符是哪幾個捕獲的子表達式,返回值可以直接由模版生成,不需要js操作】
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"
就是新的模板。
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),它代表着:
- p 前面的位置,可以定位到這個位置,用replace方法去插入指定字符;
- 同樣也可以是一個條件:在該位置後面必須有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)肯定預查,它代表着:
- p 後面的位置,可以定位到這個位置,用replace方法去插入指定字符;
- 同樣也可以是一個條件:在該位置前面必須有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