
一、正則表達式簡介
1、什么是正則表達式
正則表達式,又稱規則表達式。(英語:Regular Expression,在代碼中常簡寫為regex、regexp或RE),計算機科學的一個概念。正則表達式通常被用來檢索、替換那些符合某個模式(規則)的文本。
簡單的說,就是按照某種規則去匹配符合條件的字符串。
2、可視化正則表達式工具
Regexper:https://regexper.com/
二、RegExp對象
實例化RegExp
的兩種方式。
兩種方式定義RegExp對象。
1、字面量
let reg = /[a-z]{3}/gmi;
let reg = /[a-z]{3}/g;
let reg = /[a-z]{3}/m;
let reg = /[a-z]{3}/i;
標志
-
g
global 代表全局搜索。如果不添加,搜索到第一個匹配停止。
-
m
Multi-Line 代表多行搜索。
-
i
ignore case 代表大小寫不敏感,默認大小寫敏感。
2、構造函數
let reg = new RegExp('\\bis\\b', 'g');
因為JavaScript字符串中\
屬于特殊字符,需要轉義。
三、元字符
把元字符當作轉義字符。
正則表達式有兩種基本字符類型組成。
1、原義文本字符
表示原本意義上是什么字符,就是什么字符。
2、元字符
是在正則表達式中有特殊含義的非字母字符。
* + ? $ ^ . | \ ( ) { } [ ]
字符 |
含義 |
\t |
水平制表符 |
\v |
垂直制表符 |
\n |
換行符 |
\r |
回車符 |
\0 |
空字符 |
\f |
換頁符 |
\cX |
控制字符,與X對應的控制字符(Ctrl + X) |
類似于轉義字符。
四、字符類
表示符合某種特性的字符類別。
使用元字符[]
可以構建一個簡單的類。
所謂類是指符合某些特性的對象,一個泛指,而不是某個字符。
例子
表達式[abc]
把字符a
或b
或c
歸為一類,表達式可以匹配這一類中的任意一個字符。
'a1b2c3d4e5'.replace(/[abc]/g, '0');
字符類取反
我們想要替換不是abc
中任意一個字符的字符。
'abcdefg'.replace(/[^abc]/g, '0');
五、范圍類
匹配這一個范圍內的字符。
如果我們想要匹配數字0-9
,那么我們可能會這樣寫[0123456789]
。
如果我們想要匹配26
個字母,那么我們可能會這樣寫[abcdefghijklmnopqrstuvwxyz]
。
這樣略顯麻煩,所以才會有范圍類。
例子
'a1c2d3e4f5'.replace(/[0-9]/g, 'x');
'a1c2d3e4f5'.replace(/[a-z]/g, 'x');
'a1C2d3E4f5G6'.replace(/[a-zA-Z]/g, '*');
疑問
如果我想替換數字,并且連帶-
符號也一起替換呢?
'2018-5-21'.replace(/[0-9-]/g, '*');
六、預定義類
一些已經定義的類,可以直接使用。
字符 |
等價類 |
含義 |
. |
[^\r\n] |
除了回車、換行之外的所有字符 |
\d |
[0-9] |
數字字符 |
\D |
[^0-9] |
非數字字符 |
\s |
[\t\n\x0B\r] |
空白符 |
\S |
[^\t\n\x0B\r] |
非空白符 |
\w |
[a-zA-Z_0-9] |
單詞字符(字母、數字、下劃線) |
\W |
[^a-zA-Z_0-9] |
非單詞字符 |
例子
替換一個 ab
+ 數字
+ 任意字符
的字符串
'ab0c'.replace(/ab[0-9][^\r\n]/g, 'TangJinJian');
'ab0c'.replace(/ab\d./g, 'TangJinJian');
七、單詞邊界
字符 |
含義 |
^ |
以xxx開始(不在中括號內時的含義) |
$ |
以xxx結束 |
\b |
單詞邊界 |
\B |
非單詞邊界 |
例子
我想替換的字符串,屬于那種只在開頭出現的。
'YuYan is a boy, YuYan'.replace(/^YuYan/g, 'TangJinJian');
我想替換的字符串,屬于那種只在結尾出現的。
'YuYan is a boy, YuYan'.replace(/YuYan$/g, 'TangJinJian');
單詞邊界例子。
'This is a man'.replace(/is/g, '0');
'This is a man'.replace(/\bis/g, '0');
'This is a man'.replace(/\Bis\b/g, '0');
八、量詞
用來處理連續出現的字符串。
字符 |
含義 |
? |
出現零次或一次(最多出現一次) |
+ |
出現一次或多次(至少出現一次) |
* |
出現零次或多次(任意次) |
{n} |
出現n次 |
{n,m} |
出現n到m次 |
{n,} |
至少出現n次 |
我想替換字符串中連續出現10
次的數字為*
。
'1234567890abcd'.replace(/\d{10}/, '*');
我想替換字符串中的QQ號碼。
'我的QQ是:10000'.replace(/[1-9][0-9]{4,}/, '19216811');
九、貪婪模式
盡可能多的匹配。
有這樣的一種場景下的正則表達式,/\d{3,6}/
該替換3個數字還是6個數字呢,4、5個數字?
'123456789'.replace(/\d{3,6}/, 'x');
'123456789'.replace(/\d+/, 'x');
'123456789'.replace(/\d{3,}/, 'x');
十、非貪婪模式
盡可能少的匹配。
如果我們想要最低限度的替換呢?
'12345678'.replace(/\d{3,6}?/g, 'x');
'123456789'.replace(/\d{3,6}?/g, 'x');
因為有g
標志,會匹配這段字符串里所有符合規則的字符串。
第一個規則/\d{3,6}?/g
,12345678
中有兩個符合條件的字符串,是123
和456
。所以替換結果是xx78
。
第二個規則/\d{3,6}?/g
,123456789
中有三個符合條件的字符串,是123
、456
和789
。所以替換結果是xxx
。
十一、分組
括號里的一些規則,分為一組。
我想替換連續出現3次的字母
和數字
。
'a1b2d3c4'.replace(/[a-z]\d{3}/g, '*');
'a1b2d3c4'.replace(/([a-z]\d){3}/g, '*');
1、或
分組里有兩種規則,只要滿足其中一種即可匹配。
'ijabxyijcdxy'.replace(/ij(ab|cd)xy/g, '*');
2、反向引用
可以把分組視為變量,來引用。
'2018-5-22'.replace(/(\d{4})-(\d{1,2})-(\d{1,2})/g, '$1/$2/$3');
'2018-5-22'.replace(/(\d{4})-(\d{1,2})-(\d{1,2})/g, '$2/$3/$1');
3、忽略分組
忽略掉分組,不捕獲分組,只需要在分組內加上?:
'2018-5-22'.replace(/(?:\d{4})-(\d{1,2})-(\d{1,2})/g, '$1/$2/$3');
十二、前瞻
正則表達式從文本頭部向尾部開始解析,文本尾部方向,稱為“前”。
前瞻就是在正在表達式匹配到規則的時候,向前檢查是否符合斷言,后顧/后瞻方向相反。
JavaScript不支持后顧。
符合和不符合特定斷言稱為肯定/正向
匹配和否定/負向
匹配。
名稱 |
正則 |
含義 |
正向前瞻 |
exp(?=assert) |
|
負向前瞻 |
exp(?!assert) |
|
正向后顧 |
exp(?<=assert) |
JavaScript不支持 |
負向后顧 |
exp(?<!assert) |
JavaScript不支持 |
例子
有這樣一個單詞字符
+數字
格式的字符串,只要滿足這種格式,就把其中的單詞字符替換掉。
'a1b2ccdde3'.replace(/\w(?=\d)/g, '*');
有這樣一個單詞字符
+非數字
格式的字符串,只要滿足這種格式,就把前面的單詞字符替換掉。
'a1b2ccdde3'.replace(/\w(?!\d)/g, '*');
十三、RegExp對象屬性
global
是否全文搜索,默認false
。
ignore case
是否大小寫敏感,默認是false
。
multiline
多行搜索,默認值是false
。
lastIndex
是當前表達式匹配內容的最后一個字符的下一個位置。
source
正則表達式的文本字符串。
let reg1 = /\w/;
let reg2 = /\w/gim;
reg1.global;
reg1.ignoreCase;
reg1.multiline;
reg2.global;
reg2.ignoreCase;
reg2.multiline;
十四、RegExp對象方法
1、RegExp.prototype.test()
用來查看正則表達式與指定的字符串是否匹配。返回true
或false
。
let reg1 = /\w/;
reg1.test('a');
reg1.test('*');
加上g
標志之后,會有些區別。
let reg1 = /\w/g;
reg1.test('ab');
reg1.test('ab');
reg1.test('ab');
reg1.test('ab');
reg1.test('ab');
reg1.test('ab');
實際上這是因為RegExp.lastIndex
。每次匹配到之后,lasgIndex
會改變。
lastIndex
是正則表達式的一個可讀可寫的整型屬性,用來指定下一次匹配的起始索引。
let reg = /\w/g;
while(reg.test('ab')) {
console.log(reg.lastIndex);
}
reg.lastIndex
初始時為0
,第一個次匹配到a
的時候,reg.lastIndex
為1
。第二次匹配到b
的時候,reg.lastIndex
為2
。
let reg = /\w\w/g;
while(reg.test('ab12cd')) {
console.log(reg.lastIndex);
}
reg.lastIndex
初始時為0
,第一個次匹配到ab
的時候,reg.lastIndex
為2
。第二次匹配到12
的時候,reg.lastIndex
為4
。第三次匹配到cd
的時候,reg.lastIndex
為6
。
let reg = /\w/g;
while(reg.test('ab')) {
console.log(reg.lastIndex);
}
console.log(reg.lastIndex);
reg.test('ab');
console.log(reg.lastIndex);
所以,這就是為什么reg.test('ab')
再多次執行之后,返回值為false
的原因了。
let reg = /\w/g;
reg.lastIndex = 2;
reg.test('ab');
每次匹配的起始位置,是以lastIndex
為起始位置的。上述例子,一開始從位置2
開始匹配,位置2
后面沒有符合正則的字符串,所以為false
。
2、RegExp.prototype.exec()
在一個指定字符串中執行一個搜索匹配。返回一個搜索的結果數組或null
。
非全局情況
let reg = /\d(\w)\d/;
let ts = '*1a2b3c';
let ret = reg.exec(ts);
console.log(reg.lastIndex + '\t' + ret.index + '\t' + ret.toString());
console.log(ret);
返回數組是有以下元素組成的:
-
第一個元素是與正則表達式相匹配的文本。
-
第二個元素是
reg
對象的第一個子表達式相匹配的文本(如果有的話)。
-
第二個元素是
reg
對象的第二個子表達式相匹配的文本(如果有的話),以此類推。
let reg = /\d(\w)(\w)(\w)\d/;
let ts = '*1a2b3c';
let ret = reg.exec(ts);
console.log(reg.lastIndex + '\t' + ret.index + '\t' + ret.toString());
console.log(ret);
全局情況
let reg = /\d(\w)(\w)(\w)\d/g;
let ts = '*1abc25def3g';
while(ret = reg.exec(ts)) {
console.log(reg.lastIndex + '\t' + ret.index + '\t' + ret.toString());
}
第一次匹配的是1abc2
,1abc2
的后一個字符的起始位置是6
,所以reg.lastIndex
是6
。
1abc2
的第一個字符的起始位置是1
,所以ret.index
是1
。
第二次匹配的是5def3
,5def3
的后一個字符的起始位置是11
,所以reg.lastIndex
是11
。
5def3
的第一個字符的起始位置是6
,所以ret.index
是6
。
十五、字符串對象方法
1、String.prototype.search()
執行正則表達式和String
對象之間的一個搜索匹配。
方法返回第一個匹配項的index
,搜索不到返回-1
。
不執行全局匹配,忽略g
標志,并且總是從字符串的開始進行檢索。
我想知道Jin
字符串的起始位置在哪里。
'TangJinJian'.search('Jin');
'TangJinJian'.search(/Jin/);
search
方法,既可以通過字符串,也可以通過正則描述字符串來搜索匹配。
2、String.prototype.match()
當一個字符串與一個正則表達式匹配時, match()
方法檢索匹配項。
提供RegExp
對象參數是否具有g
標志,對結果影響很大。
非全局調用的情況
如果RegExp
沒有g
標志,那么match
只能在字符串中,執行一次匹配。
如果沒有找到任何匹配文本,將返回null
。
否則將返回一個數組,其中存放了與它找到的匹配文本有關的信息。
let reg = /\d(\w)\d/;
let ts = '*1a2b3c';
let ret = ts.match(reg);
console.log(ret.index + '\t' + reg.lastIndex);
console.log(ret);
非全局情況下和RegExp.prototype.exec()
方法的效果是一樣的。
全局調用的情況
我想找到所有數字
+單詞
+數字
格式的字符串。
let reg = /\d(\w)\d/g;
let ts = '*1a2b3c4e';
let ret = ts.match(reg);
console.log(ret.index + '\t' + reg.lastIndex);
console.log(ret);
全局情況下和RegExp.prototype.exec()
方法的區別。在于,沒有了分組信息。
如果我們不使用到分組信息,那么使用String.prototype.match()
方法,效率要高一些。而且不需要寫循環來逐個所有的匹配項獲取。
3、String.prototype.split()
使用指定的分隔符字符串將一個String
對象分割成字符串數組。
'a,b,c,d'.split(/,/);
'a1b2c3d'.split(/\d/);
'a1b-c|d'.split(/[\d-|]/);
4、String.prototype.replace()
返回一個由替換值替換一些或所有匹配的模式后的新字符串。模式可以是一個字符串或者一個正則表達式, 替換值可以是一個字符串或者一個每次匹配都要調用的函數。
常規用法
'TangJinJian'.replace('Tang', '');
'TangJinJian'.replace(/Ji/g, '*');
以上兩種用法,是最常用的,但是還不能精細化控制。
精細化用法
我想要把a1b2c3d4
中的數字都加一,變成a2b3c4d5
。
'a1b2c3d4'.replace(/\d/g, function(match, index, orgin) {
console.log(index);
return parseInt(match) + 1;
});
回調函數有以下參數:
-
match
第一個參數。匹配到的字符串。
-
group
第二個參數。分組,如果有n個分組,則以此類推n個group
參數,下面兩個參數將變為第2+n
和3+n
個參數。沒有分組,則沒有該參數。
-
index
第三個參數。匹配到的字符串第一個字符索引位置。
-
orgin
第四個參數。源字符串。
我想把兩個數字之間的字母去掉。
'a1b2c3d4e5f6'.replace(/(\d)(\w)(\d)/g, function(match, group1, group2, group3, index, orgin) {
console.log(match);
return group1 + group3;
});