系統認識JavaScript正則表達式

版權聲明

轉載請告知並註明來源做者
做者唐金健
網絡暱稱御焱
掘金 知乎 思否 專欄優雅的前端

1、正則表達式簡介

一、什麼是正則表達式

正則表達式,又稱規則表達式。(英語:Regular Expression,在代碼中常簡寫爲regex、regexp或RE),計算機科學的一個概念。正則表達式一般被用來檢索、替換那些符合某個模式(規則)的文本。前端

簡單的說,就是按照某種規則去匹配符合條件的字符串。

二、可視化正則表達式工具

Regexper:https://regexper.com/正則表達式

2、RegExp對象

實例化 RegExp的兩種方式。

兩種方式定義RegExp對象。segmentfault

一、字面量

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 表明大小寫不敏感,默認大小寫敏感。

二、構造函數

let reg = new RegExp('\\bis\\b', 'g');

由於JavaScript字符串中\屬於特殊字符,須要轉義。數組

3、元字符

把元字符看成轉義字符。

正則表達式有兩種基本字符類型組成。網絡

  • 原義文本字符
  • 元字符

一、原義文本字符

表示本來意義上是什麼字符,就是什麼字符。函數

二、元字符

是在正則表達式中有特殊含義的非字母字符。
* + ? $ ^ . | \ ( ) { } [ ]工具

字符 含義
\t 水平製表符
\v 垂直製表符
\n 換行符
\r 回車符
\0 空字符
\f 換頁符
\cX 控制字符,與X對應的控制字符(Ctrl + X)

相似於轉義字符。post

4、字符類

表示符合某種特性的字符類別。

使用元字符[]能夠構建一個簡單的類。
所謂類是指符合某些特性的對象,一個泛指,而不是某個字符。spa

例子

表達式[abc]把字符abc歸爲一類,表達式能夠匹配這一類中的任意一個字符。prototype

// replace() 方法用於在字符串中用一些字符替換另外一些字符,或替換一個與正則表達式匹配的子串。
'a1b2c3d4e5'.replace(/[abc]/g, '0');  //010203d4e5

字符類取反

咱們想要替換不是abc中任意一個字符的字符。

// 元字符 ^ 建立一個 反向類/負向類
'abcdefg'.replace(/[^abc]/g, '0');  //abc0000

5、範圍類

匹配這一個範圍內的字符。

若是咱們想要匹配數字0-9,那麼咱們可能會這樣寫[0123456789]
若是咱們想要匹配26個字母,那麼咱們可能會這樣寫[abcdefghijklmnopqrstuvwxyz]
這樣略顯麻煩,因此纔會有範圍類。

例子

// 替換全部數字
'a1c2d3e4f5'.replace(/[0-9]/g, 'x');  //axcxdxexfx
// 替換全部小寫字母
'a1c2d3e4f5'.replace(/[a-z]/g, 'x');  //x1x2x3x4x5
// []組成的類內部是能夠連寫的。替換全部大小寫字母
'a1C2d3E4f5G6'.replace(/[a-zA-Z]/g, '*');  //*1*2*3*4*5*6

疑問

若是我想替換數字,而且連帶-符號也一塊兒替換呢?

// 替換全部數字和橫槓
'2018-5-21'.replace(/[0-9-]/g, '*');  //*********

6、預約義類

一些已經定義的類,能夠直接使用。
字符 等價類 含義
. [^\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 + 數字 + 任意字符 的字符串

// 寫法1
'ab0c'.replace(/ab[0-9][^\r\n]/g, 'TangJinJian');  //TangJianJian
// 寫法2
'ab0c'.replace(/ab\d./g, 'TangJinJian');  //TangJianJian

7、單詞邊界

字符 含義
^ 以xxx開始(不在中括號內時的含義)
$ 以xxx結束
\b 單詞邊界
\B 非單詞邊界

例子

我想替換的字符串,屬於那種只在開頭出現的。

'YuYan is a boy, YuYan'.replace(/^YuYan/g, 'TangJinJian');  //TangJinJian is a boy, YuYan

我想替換的字符串,屬於那種只在結尾出現的。

'YuYan is a boy, YuYan'.replace(/YuYan$/g, 'TangJinJian');  //YuYan is a boy, TangJinJian

單詞邊界例子。

// 替換全部is爲0
'This is a man'.replace(/is/g, '0');  //Th0 0 a man
// 替換全部is前面帶有單詞邊界的字符串
'This is a man'.replace(/\bis/g, '0');  //This 0 a man
// 替換全部is前面沒有單詞邊界的字符串
'This is a man'.replace(/\Bis\b/g, '0');  //Th0 is a man

8、量詞

用來處理連續出現的字符串。
字符 含義
? 出現零次或一次(最多出現一次)
+ 出現一次或屢次(至少出現一次)
* 出現零次或屢次(任意次)
{n} 出現n次
{n,m} 出現n到m次
{n,} 至少出現n次

我想替換字符串中連續出現10次的數字爲*

'1234567890abcd'.replace(/\d{10}/, '*');  //*abcd

我想替換字符串中的QQ號碼。

'個人QQ是:10000'.replace(/[1-9][0-9]{4,}/, '19216811');  //個人QQ是:19216811

9、貪婪模式

儘量多的匹配。

有這樣的一種場景下的正則表達式,/\d{3,6}/該替換3個數字仍是6個數字呢,四、5個數字?

// 貪婪模式會盡量的往多的方面去匹配
'123456789'.replace(/\d{3,6}/, 'x');  //x789
'123456789'.replace(/\d+/, 'x');  //x
'123456789'.replace(/\d{3,}/, 'x');  //x

10、非貪婪模式

儘量少的匹配。

若是咱們想要最低限度的替換呢?

// 非貪婪模式使用 ? 儘量的往少的方面去匹配
'12345678'.replace(/\d{3,6}?/g, 'x');  //xx78
'123456789'.replace(/\d{3,6}?/g, 'x');  //xxx

由於有g標誌,會匹配這段字符串裏全部符合規則的字符串。
第一個規則/\d{3,6}?/g12345678中有兩個符合條件的字符串,是123456。因此替換結果是xx78
第二個規則/\d{3,6}?/g123456789中有三個符合條件的字符串,是123456789。因此替換結果是xxx

11、分組

括號裏的一些規則,分爲一組。

我想替換連續出現3次的字母數字

//沒有分組的狀況下,後面的量詞,只是表示匹配3次數字。
'a1b2d3c4'.replace(/[a-z]\d{3}/g, '*');  //a1b2d3c4
//有分組的狀況下,分組後面的量詞,表示符合這個分組裏規則的字符串,匹配3次。
'a1b2d3c4'.replace(/([a-z]\d){3}/g, '*');  //*c4

一、或

分組裏有兩種規則,只要知足其中一種便可匹配。

//我想把ijaxxy和ijcdxy都替換成*
'ijabxyijcdxy'.replace(/ij(ab|cd)xy/g, '*');  //**

二、反向引用

能夠把分組視爲變量,來引用。

//我想把改變年月日之間的分隔符
'2018-5-22'.replace(/(\d{4})-(\d{1,2})-(\d{1,2})/g, '$1/$2/$3');  //2018/5/22
//我想替換日期,而且更改順序
'2018-5-22'.replace(/(\d{4})-(\d{1,2})-(\d{1,2})/g, '$2/$3/$1');  //5/22/2018

三、忽略分組

忽略掉分組,不捕獲分組,只須要在分組內加上?:

// 忽略掉匹配年的分組後,匹配月的分組變成了$1,日的分組變成了$2
'2018-5-22'.replace(/(?:\d{4})-(\d{1,2})-(\d{1,2})/g, '$1/$2/$3');  //5/22/$3

12、前瞻

正則表達式從文本頭部向尾部開始解析,文本尾部方向,稱爲「前」。
前瞻就是在正在表達式匹配到規則的時候,向前檢查是否符合斷言,後顧/後瞻方向相反。
JavaScript不支持後顧。
符合和不符合特定斷言稱爲 確定/正向匹配和 否認/負向匹配。
名稱 正則 含義
正向前瞻 exp(?=assert)
負向前瞻 exp(?!assert)
正向後顧 exp(?<=assert) JavaScript不支持
負向後顧 exp(?<!assert) JavaScript不支持

例子

有這樣一個單詞字符+數字格式的字符串,只要知足這種格式,就把其中的單詞字符替換掉。

'a1b2ccdde3'.replace(/\w(?=\d)/g, '*');  //*1*2ccdd*3

有這樣一個單詞字符+非數字格式的字符串,只要知足這種格式,就把前面的單詞字符替換掉。

'a1b2ccdde3'.replace(/\w(?!\d)/g, '*');  //a*b*****e*

十3、RegExp對象屬性

global是否全文搜索,默認false
ignore case是否大小寫敏感,默認是false
multiline多行搜索,默認值是false
lastIndex是當前表達式匹配內容的最後一個字符的下一個位置。
source正則表達式的文本字符串。

let reg1 = /\w/;
let reg2 = /\w/gim;

reg1.global;  //false
reg1.ignoreCase;  //false
reg1.multiline;  //false

reg2.global;  //true
reg2.ignoreCase;  //true
reg2.multiline;  //true

十4、RegExp對象方法

一、RegExp.prototype.test()

用來查看正則表達式與指定的字符串是否匹配。返回 truefalse
let reg1 = /\w/;
reg1.test('a');  //true
reg1.test('*');  //false

加上g標誌以後,會有些區別。

let reg1 = /\w/g;
// 第一遍
reg1.test('ab');  //true
// 第二遍
reg1.test('ab');  //true
// 第三遍
reg1.test('ab');  //false
// 第四遍
reg1.test('ab');  //true
// 第五遍
reg1.test('ab');  //true
// 第六遍
reg1.test('ab');  //false

實際上這是由於RegExp.lastIndex。每次匹配到以後,lasgIndex會改變。
lastIndex是正則表達式的一個可讀可寫的整型屬性,用來指定下一次匹配的起始索引。

let reg = /\w/g;
// 每次匹配到,就會把lastIndex指向匹配到的字符串後一個字符的索引。
while(reg.test('ab')) {
    console.log(reg.lastIndex);
}
// 1
// 2

reg.lastIndex初始時爲0,第一個次匹配到a的時候,reg.lastIndex1。第二次匹配到b的時候,reg.lastIndex2

let reg = /\w\w/g;
while(reg.test('ab12cd')) {
  console.log(reg.lastIndex);
}
// 2
// 4
// 6

reg.lastIndex初始時爲0,第一個次匹配到ab的時候,reg.lastIndex2。第二次匹配到12的時候,reg.lastIndex4。第三次匹配到cd的時候,reg.lastIndex6

let reg = /\w/g;
// 匹配不到符合正則的字符串以後,lastIndex會變爲0。
while(reg.test('ab')) {
    console.log(reg.lastIndex);
}
console.log(reg.lastIndex);
reg.test('ab');
console.log(reg.lastIndex);
// 1
// 2
// 0
// 1

因此,這就是爲何reg.test('ab')再屢次執行以後,返回值爲false的緣由了。

let reg = /\w/g;
reg.lastIndex = 2;
reg.test('ab');  //false

每次匹配的起始位置,是以lastIndex爲起始位置的。上述例子,一開始從位置2開始匹配,位置2後面沒有符合正則的字符串,因此爲false

二、RegExp.prototype.exec()

在一個指定字符串中執行一個搜索匹配。返回一個搜索的結果數組或 null

非全局狀況

let reg = /\d(\w)\d/;
let ts = '*1a2b3c';
let ret = reg.exec(ts);  //ret是結果數組
// reg.lastIndex確定是0,由於沒有g標誌。 沒有g標誌的狀況下,lastIndex被忽略。
console.log(reg.lastIndex + '\t' + ret.index + '\t' + ret.toString());
console.log(ret);
// 0  1 1a2,a
// ["1a2", "a"]

返回數組是有如下元素組成的:

  • 第一個元素是與正則表達式相匹配的文本。
  • 第二個元素是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);  //輸出結果數組
// 0  1 1a2b3,a,2,b
// ["1a2b3", "a", "2", "b"]

全局狀況

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());
}
// 6  1 1abc2,a,b,c
// 11 6 5def3,d,e,f

第一次匹配的是1abc21abc2的後一個字符的起始位置是6,因此reg.lastIndex6
1abc2的第一個字符的起始位置是1,因此ret.index1

第二次匹配的是5def35def3的後一個字符的起始位置是11,因此reg.lastIndex11
5def3的第一個字符的起始位置是6,因此ret.index6

十5、字符串對象方法

一、String.prototype.search()

執行正則表達式和 String對象之間的一個搜索匹配。
方法返回第一個匹配項的 index,搜索不到返回 -1
不執行全局匹配,忽略 g標誌,而且老是從字符串的開始進行檢索。

我想知道Jin字符串的起始位置在哪裏。

'TangJinJian'.search('Jin');  //4
'TangJinJian'.search(/Jin/);  //4

search方法,既能夠經過字符串,也能夠經過正則描述字符串來搜索匹配。

二、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);
// 1  0
// ["1a2", "a"]

非全局狀況下和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);
// undefined  0
// ["1a2", "3c4"]

全局狀況下和RegExp.prototype.exec()方法的區別。在於,沒有了分組信息。
若是咱們不使用到分組信息,那麼使用String.prototype.match()方法,效率要高一些。並且不須要寫循環來逐個全部的匹配項獲取。

三、String.prototype.split()

使用指定的分隔符字符串將一個 String對象分割成字符串數組。
'a,b,c,d'.split(/,/);  //["a", "b", "c", "d"]
'a1b2c3d'.split(/\d/);  //["a", "b", "c", "d"]
'a1b-c|d'.split(/[\d-|]/);  //["a", "b", "c", "d"]

四、String.prototype.replace()

返回一個由替換值替換一些或全部匹配的模式後的新字符串。模式能夠是一個字符串或者一個正則表達式, 替換值能夠是一個字符串或者一個每次匹配都要調用的函數。

常規用法

'TangJinJian'.replace('Tang', '');  //JinJian
'TangJinJian'.replace(/Ji/g, '*');  //Tang*n*an

以上兩種用法,是最經常使用的,可是還不能精細化控制。

精細化用法

我想要把a1b2c3d4中的數字都加一,變成a2b3c4d5

'a1b2c3d4'.replace(/\d/g, function(match, index, orgin) {
    console.log(index);
    return parseInt(match) + 1;
});
// 1
// 3
// 5
// 7
// a2b3c4d5

回調函數有如下參數:

  • match第一個參數。匹配到的字符串。
  • group第二個參數。分組,若是有n個分組,則以此類推n個group參數,下面兩個參數將變爲第2+n3+n個參數。沒有分組,則沒有該參數。
  • index第三個參數。匹配到的字符串第一個字符索引位置。
  • orgin第四個參數。源字符串。

我想把兩個數字之間的字母去掉。

'a1b2c3d4e5f6'.replace(/(\d)(\w)(\d)/g, function(match, group1, group2, group3, index, orgin) {
  console.log(match);
  return group1 + group3;
});
// 1b2
// 3d4
// 5f6
// a12c34e56
相關文章
相關標籤/搜索