1. 程式人生 > 實用技巧 >理解Javascript的正則表示式

理解Javascript的正則表示式

正文

相信很多人第一次見到正則表示式的第一印象都是懵逼的,對新手而言一個正則表示式就是一串毫無意義的字串,讓人摸不著頭腦。但正則表示式是個非常有用的特性,不管是JavaScript、php、Java還是Python都有正則表示式。儼然正則表示式已經發展成了一門小語言。作為程式語言的一部分,它不想變數,函式,物件這種概念那麼容易理解。很多人對於正則表示式的理解都是基於簡單的匹配,等到業務中用到完全靠從網上copy來解決問題。不得不說,隨著各種開源技術社群的發展,靠copy的確能解決業務中絕大多數的問題,但作為一名有追求的程式設計師,是絕對不會讓自己僅僅依靠Ctrl C + Ctrl V來程式設計的。本文基於JavaScript的正則表示式,結合筆者個人的思考和社群內一些優秀正則表示式文章來對正則表示式進行講解。

Javascrip中的正則表示式使用方法

簡單介紹下,在Javascript中使用正則表示式有兩種方式:

建構函式:使用內建的RegExp建構函式;

字面量:使用雙斜槓(//);

使用建構函式:

varregexconst =newRegExp('abc');

使用雙斜槓:

varregexLiteral =/abc/;

匹配方法

Javascript中的正則表示式物件主要有兩個方法,test和exec:

test()方法接受一個引數,這個引數是用來與正則表示式匹配的字串,如下例子:

var regex = /hello/;
var str = 'hello world';
var result = regex.test(str);
console.log(result);
// returns true

exec()方法在一個指定字串中執行一個搜尋匹配。返回一個結果陣列或 null。

var regex = /hello/;
var str = 'hello world';
var result = regex.exec(str);
console.log(result);
// returns [ 'hello', index: 0, input: 'hello world', groups: undefined ]
// 匹配失敗會返回null
// 'hello' 待匹配的字串
// index: 正則表示式開始匹配的位置
// input: 原始字串

下文都用test()方法來進行測試。

標誌

標誌是用來表示搜尋字串範圍的一個引數,主要有6個標誌:

標誌描述
g 全域性搜尋。
i 不區分大小寫搜尋。
m 多行搜尋。
s 允許.匹配換行符。
u 使用unicode碼的模式進行匹配。
y 執行“粘性”搜尋,匹配從目標字串的當前位置開始,可以使用y標誌。

雙斜槓語法:

varre =/pattern/flags;

建構函式語法:

varre =newRegExp("pattern","flags");

看下例項:

var reg1 = /abc/gi;
var reg2 = new RegExp("abc", "gi");
var str = 'ABC';
console.log(reg1.test(str)); // true
console.log(reg2.test(str)); // true

正則表示式的思考

正則表示式是對字串進行匹配的一種模式。

請記住,正則表示式是對字串的操作,所以一般具有字串型別的程式語言都會有正則表示式。

對於字串而言,是由兩部分構成的:內容和位置

比如一個字串:

'hello World';

它的內容就是:

'h','e','l','l','o',' ','W','o','r','l','d'

如上字串中每一個獨立的字母就是這個字串的內容,而位置指的是:

位置所指就是相鄰字元之間的位置,也就是上圖中箭頭的位置。

匹配內容相比匹配位置來說更為複雜,先看下簡單的匹配方式:

簡單匹配

最簡單的匹配方式就是完整的去匹配一個字串:

 var regex = /hello/;
 console.log(regex.test('hello world'));
 // true

複雜匹配

正則表示式中有很多特殊字元用來匹配字串,解決的就是匹配多少(按位置匹配)和匹配誰(按內容匹配)的問題。我們先來看下用來決定匹配誰的一些特殊字元:

匹配內容

簡單的特殊字元

簡單的匹配內容有如下的特殊字元:

[xyz]:字符集,用來匹配方括號中的任意一個字元,比如:

var regex = /[bt]ear/;
console.log(regex.test('tear'));
// returns true
console.log(regex.test('bear'));
// return true
console.log(regex.test('fear'));
// return false

注意:除了特殊字元^外,其它所有的特殊字元在字符集(方括號中)都會失去它的特殊含義。

[^xyz]:這也是個字符集,和上面字符集不同的事,它用來匹配所有不在方括號中的字元。比如:

var regex = /[^bt]ear/;
console.log(regex.test('tear'));
// returns false
console.log(regex.test('bear'));
// return false
console.log(regex.test('fear'));
// return true

針對小寫字母,大寫字母和數字這三種非常常用的字元,還提供了比較簡便的寫法:

\d:相當於[0-9],匹配數字字元。

\D:相當於[^0-9],匹配非數字的字元。

\w:相當於[a-zA-Z0–9_],匹配數字、小寫字母、大寫字母和下劃線。

\W:相當於[^A-Za-z0-9_],匹配非數字、非小寫字母、非大寫字母和非下劃線。

[a-z]:假如我們想匹配所有的字母,一個笨辦法就是將所有的字母都寫到方括號裡,但很明這種實現很不優雅,不易讀而且很容易遺漏字母。這裡有一種更簡單的實現方案,就是指定字元範圍,比如[a-h]就是匹配字母a到字母h之間所有的字母,除了小寫字母還可以匹配數字和大寫字母,[0-9]匹配0到9之間的數字,[A-Z]匹配A到Z之間所有的大寫字母。比如:

var regex = /[a-z0-9A-Z]ear/;
console.log(regex.test('fear'));
// returns true
console.log(regex.test('tear'));
// returns true
console.log(regex.test('1ear'));
// returns true
console.log(regex.test('Tear'));
// returns true

x|y:匹配x或是y。比如:

var regex = /(green|red) apple/;
console.log(regex.test('green apple'));
// true
console.log(regex.test('red apple'));
// true
console.log(regex.test('blue apple'));
// false

.: 匹配除換行符之外的任何單個字元,如果標誌中有s則也會匹配換行符例子:

var regex = /.n/ ;
console.log(regex.test('an'));
// true
console.log(regex.test('no'));
// false
console.log(regex.test('on'));
// true
console.log(regex.test(`
n`));
// false
console.log(/.n/s.test(`
n`)); // 注意這裡的正則
// true

\:這個特殊字元是用來轉義的,比如我們想匹配方括號,就可以用\轉義,同樣相匹配也可以用\轉義,比如:

var regex = /\[\]/;
console.log(regex.test('[]')); // true

上面的特殊字元都只能匹配某個目標字串一次,但很多場景下我們需要匹配目標字串多次,比如我們想匹配無數個a,上面的特殊字元就無法滿足我們的需求了,因此匹配內容的特殊字元裡還有一部分是用來解決這個問題的:

{n}:匹配大括號之前的字元n次。例子:

var regex = /go{2}d/;
console.log(regex.test('good'));
// true
console.log(regex.test('god'));
// false

很好理解,上面的正則相當於/good/。

{n,}:匹配大括號之前的字元至少n次。例子:

var regex = /go{2,}d/;
console.log(regex.test('good'));
// true
console.log(regex.test('goood'));
// true
console.log(regex.test('gooood'));
// true

{n,m}:匹配大括號之前的字元至少n次,至多m次。例子:

var regex = /go{1,2}d/;
console.log(regex.test('god'));
// true
console.log(regex.test('good'));
// true
console.log(regex.test('goood'));
// false

為了更為方便的使用,還提供了三個比較常用規則更為方便的寫法:

*:相當於{0,}。表示前面的字元至少出現0次,也就是出現任意次。

+:相當於{1,}。表示前面的字元至少出現1次。

?:相當於{0,1}。表示前面的字元不出現或是出現1次。

使用以上內容匹配普通的字元已經可以滿足需求了,但像換行符、換頁符和回車等特殊的符號以上的特殊字元無法滿足需求,因此正則表示式還提供了專門用來匹配特殊符號的特殊字元:

\s:匹配一個空白字元,包括空格、製表符、換頁符和換行符。看下例子:

var reg = /\s/;
console.log(reg.test(' ')); // true

\S:匹配一個非空白字元;

\t:匹配一個水平製表符 。

\n:匹配一個換行符。

\f:匹配一個換頁符。

\r:匹配一個回車符。

\v:匹配一個垂直製表符。

\0:匹配 NULL(U+0000)字元。

[\b]:匹配一個退格。

\cX:當X是處於A到Z之間的字元的時候,匹配字串中的一個控制符。

內容匹配進階

(x): 匹配x並記住x,括號內的內容被稱為捕獲組。這個括號裡強大的是可以支援子表示式,就是說可以在括號裡去寫正則,然後作為一個整體去匹配。這裡還有一個特殊字元叫\n,這個n和前面換行符不一樣,這是個變數指的是數字,用來記錄捕獲組序號的。例子:

console.log(/(foo)(bar)\1\2/.test('foobarfoobar')); // true
console.log(/(\d)([a-z])\1\2/.test('1a1a')); // true
console.log(/(\d)([a-z])\1\2/.test('1a2a')); // false
console.log(/(\d){2}/.test('12')); // true

在正則表示式的替換環節,則要使用像$1、$2、...、$n這樣的語法,例如,'bar foo'.replace(/(...) (...)/, '$2 $1')。$&表示整個用於匹配的原字串。

(?:x):匹配 'x' 但是不記住匹配項。被稱為非捕獲組。這裡的1不會生效,會把它當做普通字元處理。例子:

var regex = /(?:foo)bar\1/;
console.log(regex.test('foobarfoo'));
// false
console.log(regex.test('foobar'));
// false
console.log(regex.test('foobar\1'));
// true

匹配位置

再次強調,這裡的位置是前面圖裡箭頭的位置。

^:匹配字串的開始位置,也就是我們前面位置圖的第一個箭頭的位置。注意和[^xy]中的^區分,兩個含義完全不同,看^例子:

var regex = /^g/;
console.log(regex.test('good'));
// true
console.log(regex.test('bad'));
// false
console.log(regex.test('tag'));
// false

上面正則的含義即匹配字母g開頭的字串。

$:匹配字串的結束位置,例子:

var regex = /.com$/;
console.log(regex.test('[email protected]'));
// true
console.log(regex.test('test@testmail'));
// false

上面正則的含義即匹配以.com為結尾的字串

\b:匹配一個詞的邊界。注意匹配的是一個詞的邊界,這個邊界指的是一個詞不被另外一個“字”字元跟隨的位置或者前面跟其他“字”字元的位置。也就是符合要求的某個位置兩邊不全是正常字元或不全是特殊符號的。看例子:

console.log(/\bm/.test('moon')); // true 匹配“moon”中的‘m’,\b的左邊是空字串,右邊是'm'
console.log(/oo\b/.test('moon')); // false 並不匹配"moon"中的'oo',因為 \b左邊上oo,右邊是n,全是正常字元
console.log(/oon\b/.test('moon')); // true 匹配"moon"中的'oon',\b左邊是oon,右邊是空字串
console.log(/n\b/.test('moon   ')); // true 匹配"moon"中的'n',\b左邊是n,右邊是空格
console.log(/\bm/.test('   moon')); // true 匹配"moon"中的'm',\b左邊是空字串 右邊是m
console.log(/\b/.test('  ')); // false 無法匹配空格,\b左邊是空格或空字串,右邊是空格或是空字串,無法滿足不全是正常字元或是不全是正常字元

這個如果不好理解,可以先看\B,更好理解一點。

\B: 匹配一個非單詞邊界,和\b相反,也就是說匹配的是左右兩邊全是正常字元或全是特殊符號的位置。看例子:

console.log(/\B../.test('moon')); // true 匹配'moon'中的'oo' \B左邊是m,右邊是o
console.log(/\B./.exec('  ')); // true 匹配'  '中的' ' \B左邊是空字串,右邊是空格' '

x(?!y):僅僅當'x'後面不跟著'y'時匹配'x',這被稱為正向否定查詢。例子:

var regex = /Red(?!Apple)/;
console.log(regex.test('RedOrange')); // true

(?<!y)x:僅僅當'x'前面不是'y'時匹配'x',這被稱為反向否定查詢。例子:

var regex = /(?<!Red)Apple/;
console.log(regex.test('GreenApple')); // true

x(?=)y:匹配'x'僅僅當'x'後面跟著'y'.這種叫做先行斷言。例子:

var regex = /Red(?=Apple)/;
console.log(regex.test('RedApple')); // true

(?<=y)x:匹配'x'僅僅當'x'前面是'y'.這種叫做後行斷言。例子:

var regex = /(?<=Red)Apple/;
console.log(regex.test('RedApple')); // true

js中可以使用正則表示式的方法

方法描述
RegExp.prototype.exec 一個在字串中執行查詢匹配的RegExp方法,它返回一個數組(未匹配到則返回 null)。
RegExp.prototype.test 一個在字串中測試是否匹配的RegExp方法,它返回 true 或 false。
String.prototype.match 一個在字串中執行查詢匹配的String方法,它返回一個數組,在未匹配到時會返回 null。
String.prototype.matchAll 一個在字串中執行查詢所有匹配的String方法,它返回一個迭代器(iterator)。
String.prototype.search 一個在字串中測試匹配的String方法,它返回匹配到的位置索引,或者在失敗時返回-1。
String.prototype.replace 一個在字串中執行查詢匹配的String方法,並且使用替換字串替換掉匹配到的子字串。
String.prototype.split 一個使用正則表示式或者一個固定字串分隔一個字串,並將分隔後的子字串儲存到陣列中的String方法。

練習

匹配任意10位數:

var regex = /^\d{10}$/;
console.log(regex.test('9995484545'));
// true

分析下上面的正則:

我們匹配想要跨越整個字串,不能字串中有我們要匹配的內容就可以,因此使用^和$限制了開頭和結尾;

\d用來匹配數字,它相當於[0-9];

{10}匹配了\d表示式,即\d重複10次;

匹配日期格式DD-MM-YYYY或DD-MM-YY:

var regex = /^(\d{1,2}-){2}\d{2}(\d{2})?$/;
console.log(regex.test('10-01-1990'));
// true
console.log(regex.test('2-01-90'));
// true
console.log(regex.test('10-01-190'));

分析上面的正則:

同理我們使用^和$限制了開頭和結尾;

\d{1,2},表示匹配1位或2位數字;

-來匹配連字元,無特殊含義;

()包裹了一個子表示式,也叫捕獲組;

{2}表示匹配上面的子表示式兩次;

\d{2}匹配兩位數字;

(\d{2})?子表示式中匹配兩位數字,然後匹配子表示式一次或是不匹配;

廣州品牌設計公司https://www.houdianzi.com

駝峰命名轉下劃線命名:

var reg = /(\B[A-Z])/g;
'oneTwoThree'.replace(reg, '_$1').toLowerCase();

分析上面的正則:

\B避免將首字母大寫的字元也轉換掉;

([A-Z])捕獲組捕獲大寫字母;

然後replace裡使用$n這樣的語法來表示前面的捕獲;

呼叫toLowerCase轉為小寫字母;

結論

正則表示式各種規則很難記,希望本篇文章可以幫大家更好的去記憶這些特殊字元,也希望大家能寫出牛叉的正則表示式。共勉。