javascript中正規表示式的基礎語法

小火柴的藍色理想發表於2016-06-23

前面的話

  正規表示式在人們的印象中可能是一堆無法理解的字元,但就是這些符號卻實現了字串的高效操作。通常的情況是,問題本身並不複雜,但沒有正規表示式就成了大問題。javascript中的正規表示式作為相當重要的知識,本文將介紹正規表示式的基礎語法

 

定義

  正規表示式(Regular Expression)是一門簡單語言的語法規範,是強大、便捷、高效的文字處理工具,它應用在一些方法中,對字串中的資訊實現查詢、替換和提取操作

  javascript中的正規表示式用RegExp物件表示,有兩種寫法:一種是字面量寫法;另一種是建構函式寫法

Perl寫法

  正規表示式字面量寫法,又叫Perl寫法,因為javascript的正規表示式特性借鑑自Perl

  正規表示式字面量定義為包含在一對斜槓(/)之間的字元,並且可以設定3個標誌

var expression = /pattern/flags;

  正規表示式的匹配模式支援下列3個標誌:

  g:表示全域性(global)模式,即模式將被應用於所有字串,而並非在發現第一個匹配項時立即停止

  i:表示不區分大小寫(case-insensitive)模式,即在確定匹配項時忽略模式與字串的大小寫

  m:表示多行(multiline)模式,即在到達一行文字末尾時還會繼續查詢下一行中是否存在與模式匹配的項

//匹配字串所有'at'的例項
var p = /at/g;
//test()方法返回一個布林值表示是否可以找到匹配項
console.log(p.test('ata'));//true
console.log(p.test('aba'));//false

RegExp建構函式

  和普通的內建物件一樣,RegExp正規表示式物件也支援new RegExp()建構函式的形式

  RegExp建構函式接收兩個引數:要匹配的字串模式(pattern)和可選的標誌字串(flags),標誌字串和字面量的三個標誌含義相同:'g'、'i'、'm'

  RegExp建構函式的兩個引數都是字串。且使用字面量形式定義的任何表示式都可使用建構函式

//匹配字串所有'at'的例項
var p1 = /at/g;
//同上
var p2 = new RegExp('at','g');

  [注意]ES3規範規定,一個正規表示式直接量會在執行到它時轉換為一個RegExp物件,同一段程式碼所表示正規表示式直接量的每次運算都返回同一個物件。ES5規範則做了相反的規定,同一段程式碼所表示的正規表示式直接量的每次運算都返回新物件。IE6-8一直是按照ECMAScript5規範的方式實現的,所以並沒有相容性問題

 

特點

  javascript中的正規表示式最大的特點是不支援空白,必須寫在一行中

//匹配ab
console.log(/ab/.test('ab')); //true
console.log(/ ab/.test('ab')); //false
console.log(/a b/.test('ab')); //false
console.log(/ab /.test('ab')); //false

 

元字元

  大部分字元在正規表示式中,就是字面的含義,比如/a/匹配a,/b/匹配b

/dog/.test("old dog") // true

  但還有一些字元,它們除了字面意思外,還有著特殊的含義,這些字元就是元字元

  在javascript中,共有14個元字元(meta-character)

() [] {} \ ^ $ | ? * + . 
元字元         名稱              匹配物件
.             點號               單個任意字元(除回車\r、換行\n、行分隔符\u2028和段分隔符\u2029外)
[]            字元組             列出的單個任意字元
[^]           排除型字元組        未列出的單個任意字元
?             問號               匹配0次或1次
*             星號               匹配0交或多次
+             加號               匹配1次或多次
{min,max}     區間量詞            匹配至少min次,最多max次
^             脫字元             行的起始位置
$             美元符             行的結束位置
|             豎線               分隔兩邊的任意一個表示式
()            括號               限制多選結構的範圍,標註量詞作用的元素,為反向引用捕獲文字
\1,\2...      反向引用            匹配之前的第一、第二...組括號內的表示式匹配的文字

 

轉義字元

  轉義字元(escape)表示為反斜線(\)+字元的形式,共有以下3種情況

  【1】因為元字元有特殊的含義,所以無法直接匹配。如果要匹配它們本身,則需要在它們前面加上反斜槓(\)

console.log(/1+1/.test('1+1')); //false
console.log(/1\+1/.test('1+1')); //true

console.log(/\*/.test('*')); //true
console.log(/*/.test('*')); //報錯

  但實際上,並非14個元字元都需要轉義,右方括號]和右花括號}不需要轉義

console.log(/]/.test(']')); //true
console.log(/\]/.test(']')); //true

console.log(/\}/.test('}')); //true
console.log(/}/.test('}')); //true

  【2】'\'加非元字元,表示一些不能列印的特殊字元

\0        NUL字元\u0000
[\b]      匹配退格符\u0008,不要與\b混淆
\t        製表符\u0009
\n        換行符\u000A
\v        垂直製表符\u000B
\f        換頁符\u000C
\r        回車符\u000D
\xnn      由十六進位制數nn指定的拉丁字元
\uxxxx    由十六進位制數xxxx指定的Unicode字元(\u4e00-\u9fa5代表中文)  
\cX       控制字元^X,表示ctrl-[X],其中的X是A-Z之中任一個英文字母,用來匹配控制字元

  【3】'\'加任意其他字元,預設情況就是匹配此字元,也就是說,反斜線(\)被忽略了

console.log(/\x/.test('x')); //true
console.log(/\y/.test('y')); //true
console.log(/\z/.test('z')); //true

雙重轉義

  由於RegExp建構函式的引數是字串,所以某些情況下,需要對字元進行雙重轉義

  字元\在正規表示式字串中通常被轉義為\\

var p1 = /\.at/;
//等價於
var p2 = new RegExp('\\.at');

var p1 = /name\/age/;
//等價於
var p2 = new RegExp('name\\/age');

var p1 = /\w\\hello\\123/;
//等價於
var p2 = new RegExp('\\w\\\\hello\\\\123');

 

字元組

  字元組(Character Class),有的編譯成字元類或字符集。簡單而言,就是指用方括號表示的一組字元,它匹配若干字元之一

//匹配0-9這10個數字之一
var p = /[0123456789]/;
p.test('1');//true
p.test('a');//false

  [注意]字元組中的字元排列順序並不影響字元組的功能,出現重複字元也不會影響

/[0123456789]/
//等價於
/[9876543210]/
//等價於
/[1234567890123456789]/

範圍

  正規表示式通過連字元(-)提供了範圍表示法,可以簡化字元組

/[0123456789]/
//等價於
/[0-9]/
/[abcdefghijklmnopqrstuvwxyz]/
//等價於
/[a-z]/

  連字元(-)表示的範圍是根據ASCII編碼的碼值來確定的,碼值小的在前,碼值大的在後

  所以[0-9]是合法的,而[9-0]會報錯

//匹配0-9這10個數字之一
var p1 = /[0-9]/;
p1.test('1');//true
var p2 = /[9-0]/;//報錯
p2.test('1');

  在字元組中可以同時並列多個'-'範圍

/[0-9a-zA-Z]/;//匹配數字、大寫字母和小寫字母
/[0-9a-fA-F]/;//匹配數字,大、小寫形式的a-f,用來驗證十六進位制字元

console.log(/[0-9a-fA-F]/.test('d'));//true
console.log(/[0-9a-fA-F]/.test('x'));//false

  只有在字元組內部,連字元'-'才是元字元,表示一個範圍,否則它就只能匹配普通的連字元號

  [注意]如果連字元出現在字元組的開頭或末尾,它表示的也是普通的連字元號,而不是一個範圍

//匹配中劃線
console.log(/-/.test('-'));//true
console.log(/[-]/.test('-'));//true

//匹配0-9的數字或中劃線
console.log(/[0-9]/.test('-'));//false
console.log(/[0-9-]/.test('-'));//true
console.log(/[0-9\-]/.test('-'));//true
console.log(/[-0-9]/.test('-'));//true
console.log(/[\-0-9]/.test('-'));//true

排除

  字元組的另一個型別是排除型字元組,在左方括號後緊跟一個脫字元'^'表示,表示在當前位置匹配一個沒有列出的字元 

  所以[^0-9]表示0-9以外的字元

//匹配第一個是數字字元,第二個不是數字字元的字串
console.log(/[0-9][^0-9]/.test('1e'));//true
console.log(/[0-9][^0-9]/.test('q2'));//false

  [注意]在字元組內部,脫字元'^'表示排除,而在字元組外部,脫字元'^'表示一個行錨點

  ^符號是元字元,在字元組中只要^符號不挨著左方括號就可以表示其本身含義,不轉義也可以

//匹配abc和^符號
console.log(/[a-c^]/.test('^'));//true
console.log(/[a-c\^]/.test('^'));//true
console.log(/[\^a-c]/.test('^'));//true

  在字元組中,只有^、-、[、]這4個字元可能被當做元字元,其他有元字元功能的字元都只表示其本身

console.log(/[[1]]/.test('['));//false
console.log(/[[1]]/.test(']'));//false
console.log(/[\1]/.test('\\'));//false
console.log(/[^^]/.test('^'));//false
console.log(/[1-2]/.test('-'));//false

console.log(/[\[1\]]/.test('['));//true
console.log(/[\[1\]]/.test(']'));//true
console.log(/[\\]/.test('\\'));//true
console.log(/[^]/.test('^'));//true
console.log(/[1-2\-]/.test('-'));//true
console.log(/[(1)]/.test('('));//true
console.log(/[(1)]/.test(')'));//true
console.log(/[{1}]/.test('{'));//true
console.log(/[{1}]/.test('}'));//true
console.log(/[1$]/.test('$'));//true
console.log(/[1|2]/.test('|'));//true
console.log(/[1?]/.test('?'));//true
console.log(/[1*]/.test('*'));//true
console.log(/[1+]/.test('+'));//true
console.log(/[1.]/.test('.'));//true

簡記

  用[0-9]、[a-z]等字元組,可以很方便地表示數字字元和小寫字母字元。對於這類常用字元組,正規表示式提供了更簡單的記法,這就是字元組簡記(shorthands)

  常見的字元組簡記有\d、\w、\s。其中d表示(digit)數字,w表示(word)單詞,s表示(space)空白

  正規表示式也提供了對應排除型字元組的簡記法:\D、\W、\S。字母完全相同,只是改為大寫,這些簡記法匹配的字元互補

\d     數字,等同於[0-9]
\D     非數字,等同於[^0-9]
\s     空白字元,等同於[\f\n\r\t\u000B\u0020\u00A0\u2028\u2029]
\S     非空白字元,等同於[^\f\n\r\t\u000B\u0020\u00A0\u2028\u2029]
\w     字母、數字、下劃線,等同於[0-9A-Za-z_](漢字不屬於\w)
\W     非字母、數字、下劃線,等同於[^0-9A-Za-z_]

  [注意]\w不僅包括字母、數字,還包括下劃線。在進行數字驗證時,只允許輸入字母和數字時,不可以使用\w,而應該使用[0-9a-zA-Z]

任意字元

  人們一般認為點號可以代表任意字元,其實並不是

  .點號代表除回車(\r)、換行(\n) 、行分隔符(\u2028)和段分隔符(\u2029)以外的任意字元

  妥善的利用互補屬性,可以得到一些巧妙的效果。比如,[\s\S]、[\w\W]、[\d\D]都可以表示任意字元

//匹配任意字元
console.log(/./.test('\r'));//false
console.log(/[\s\S]/.test('\r'));//true

 

量詞

  根據字元組的介紹,可以用字元組[0-9]或\d來匹配單個數字字元,如果用正規表示式表示更復雜的字串,則不太方便

//表示郵政編碼6位數字
/[0-9][0-9][0-9][0-9][0-9][0-9]/;
//等價於
/\d\d\d\d\d\d/;

  正規表示式提供了量詞,用來設定某個模式出現的次數

{n}       匹配n次
{n,m}     匹配至少n次,最多m次
{n,}      匹配至少n次
?         相當於{0,1}
*         相當於{0,}
+         相當於{1,}
//表示郵政編碼6位數字
/\d{6}/;

  美國英語和英國英語有些詞的寫法不一樣,如果traveler和traveller,favor和favour,color和colour

//同時匹配美國英語和英國英語單詞
/travell?er/;
/favou?r/;
/colou?r/;

  協議名有http和https兩種

/https?/;

  量詞廣泛應用於解析HTML程式碼。HTML是一種標籤語言,它包含各種各樣的標籤,比如<head>、<img>、<table>。它們都是從<開始,到>結束,而標籤名的長度不同

console.log(/<[^<>]+>/.test('<head>'));//true
console.log(/<[^<>]+>/.test('<img>'));//true
console.log(/<[^<>]+>/.test('<>'));//false

  HTML標籤不能為空標籤,而引號字串的兩個引號之間可以為0個字元

console.log(/'[^']*'/.test("''"));//true
console.log(/'[^']*'/.test("'abc'"));//true

貪婪模式

  預設情況下,量詞都是貪婪模式(greedy quantifier),即匹配到下一個字元不滿足匹配規則為止

//exec()方法以陣列的形式返回匹配結果
/a+/.exec('aaa'); // ['aaa']

懶惰模式

  懶惰模式(lazy quantifier)和貪婪模式相對應,在量詞後加問號'?'表示,表示儘可能少的匹配,一旦條件滿足就再不往下匹配

{n}?       匹配n次
{n,m}?     匹配至少n次,最多m次
{n,}?      匹配至少n次
??         相當於{0,1}
*?         相當於{0,}
+?         相當於{1,}
/a+?/.exec('aaa'); // ['a']

  匹配<script></script>之間的程式碼看上去很容易

/<script>[\s\S]*<\/script>/
//["<script>alert("1");</script>"]
/<script>[\s\S]*<\/script>/.exec('<script>alert("1");</script>'); 

  但如果多次出現script標籤,就會出問題

//["<script>alert("1");</script><br><script>alert("2");</script>"]
/<script>[\s\S]*<\/script>/.exec('<script>alert("1");</script><br><script>alert("2");</script>');     

  它把無用的<br>標籤也匹配出來了,此時就需要使用懶惰模式

//["<script>alert("1");</script>"]
/<script>[\s\S]*?<\/script>/.exec('<script>alert("1");</script><br><script>alert("2");</script>');     

  在javascript中,/* */是註釋的一種形式,在文件中可能出現多次,這時就必須使用懶惰模式

/\/\*[\s\S]*?\*\//
//["/*abc*/"]
/\/\*[\s\S]*?\*\//.exec('/*abc*/<br>/*123*/');

 

括號

  括號有兩個功能,分別是分組和引用。具體而言,用於限定量詞或選擇項的作用範圍,也可以用於捕獲文字並進行引用或反向引用

分組

  量詞控制之前元素的出現次數,而這個元素可能是一個字元,也可能是一個字元組,也可以是一個表示式

  如果把一個表示式用括號包圍起來,這個元素就是括號裡的表示式,被稱為子表示式

  如果希望字串'ab'重複出現2次,應該寫為(ab){2},而如果寫為ab{2},則{2}只限定b

console.log(/(ab){2}/.test('abab'));//true
console.log(/(ab){2}/.test('abb'));//false
console.log(/ab{2}/.test('abab'));//false
console.log(/ab{2}/.test('abb'));//true

  身份證長度有15位和18位兩種,如果只匹配長度,可能會想當然地寫成\d{15,18},實際上這是錯誤的,因為它包括15、16、17、18這四種長度。因此,正確的寫法應該是\d{15}(\d{3})?

  email地址以@分隔成兩段,之前的部分是使用者名稱,之後的部分是主機名

  使用者名稱允許出現數字、字母和下劃線,長度一般在1-64個字元之間,則正則可表示為/\w{1,64}/

  主機名一般表現為a.b.···.c,其中c為主域名,其他為級數不定的子域名,則正則可表示為/([-a-zA-z0-9]{1,63}\.)+[-a-zA-Z0-9]{1,63}/

  所以email地址的正規表示式如下:

    var p =/\w{1,64}@([-a-zA-z0-9]{1,63}\.)+[-a-zA-Z0-9]{1,63}/;
    console.log(p.test('q@qq.com'));//true
    console.log(p.test('q@qq'));//false
    console.log(p.test('q@a.qq.com'));//true

捕獲

  括號不僅可以對元素進行分組,還會儲存每個分組匹配的文字,等到匹配完成後,引用捕獲的內容。因為捕獲了文字,這種功能叫捕獲分組

  比如,要匹配諸如2016-06-23這樣的日期字串

/(\d{4})-(\d{2})-(\d{2})/

  與以往不同的是,年、月、日這三個數值被括號括起來了,從左到右為第1個括號、第2個括號和第3個括號,分別代表第1、2、3個捕獲組

  javascript有9個用於儲存捕獲組的建構函式屬性

RegExp.$1\RegExp.$2\RegExp.$3……到RegExp.$9分別用於儲存第一、第二……第九個匹配的捕獲組。在呼叫exec()或test()方法時,這些屬性會被自動填充
console.log(/(\d{4})-(\d{2})-(\d{2})/.test('2016-06-23'));//true
console.log(RegExp.$1);//'2016'
console.log(RegExp.$2);//'06'
console.log(RegExp.$3);//'23'
console.log(RegExp.$4);//''

  而exec()方法是專門為捕獲組而設計的,返回的陣列中,第一項是與整個模式匹配的字串,其他項是與模式中的捕獲組匹配的字串

console.log(/(\d{4})-(\d{2})-(\d{2})/.exec('2016-06-23'));//["2016-06-23", "2016", "06", "23"]

  捕獲分組捕獲的文字,不僅可以用於資料提取,也可以用於替換

  replace()方法就是用於進行資料替換的,該方法接收兩個引數,第一個引數為待查詢的內容,而第二個引數為替換的內容

console.log('2000-01-01'.replace(/-/g,'.'));//2000.01.01

  在replace()方法中也可以引用分組,形式是$num,num是對應分組的編號

//把2000-01-01的形式變成01-01-2000的形式
console.log('2000-01-01'.replace(/(\d{4})-(\d{2})-(\d{2})/g,'$3-$2-$1'));//'01-01-2000'

反向引用

  英文中不少單詞都有重疊出現的字母,如shoot或beep。若想檢查某個單詞是否包含重疊出現的字母,則需要引入反向引用(back-reference)

  反向引用允許在正規表示式內部引用之前捕獲分組匹配的文字,形式是\num,num表示所引用分組的編號

//重複字母
/([a-z])\1/
console.log(/([a-z])\1/.test('aa'));//true
console.log(/([a-z])\1/.test('ab'));//false

  反向引用可以用於建立前後聯絡。HTML標籤的開始標籤和結束標籤是對應的

//開始標籤
<([^>]+)>
//標籤內容
[\s\S]*?
//匹配成對的標籤
/<([^>]+)>[\s\S]*?<\/\1>/

console.log(/<([^>]+)>[\s\S]*?<\/\1>/.test('<a>123</a>'));//true
console.log(/<([^>]+)>[\s\S]*?<\/\1>/.test('<a>123</b>'));//false

非捕獲

  除了捕獲分組,正規表示式還提供了非捕獲分組(non-capturing group),以(?:)的形式表示,它只用於限定作用範圍,而不捕獲任何文字

  比如,要匹配abcabc這個字元,一般地,可以寫為(abc){2},但由於並不需要捕獲文字,只是限定了量詞的作用範圍,所以應該寫為(?:abc){2}

console.log(/(abc){2}/.test('abcabc'));//true
console.log(/(?:abc){2}/.test('abcabc'));//true

  由於非捕獲分組不捕獲文字,對應地,也就沒有捕獲組編號

console.log(/(abc){2}/.test('abcabc'));//true
console.log(RegExp.$1);//'abc'
console.log(/(?:abc){2}/.test('abcabc'));//true
console.log(RegExp.$1);//''

  非捕獲分組也不可以使用反向引用

/(?:123)\1/.test('123123');//false
/(123)\1/.test('123123');//true

  捕獲分組和非捕獲分組可以在一個正規表示式中同時出現

console.log(/(\d)(\d)(?:\d)(\d)(\d)/.exec('12345'));//["12345", "1", "2", "4", "5"]

 

選擇

  豎線'|'在正規表示式中表示(OR)關係的選擇,以豎線'|'分隔開的多個子表示式也叫選擇分支或選擇項。在一個選擇結構中,選擇分支的數目沒有限制

  在選擇結構中,豎線|用來分隔選擇項,而括號()用來規定整個選擇結構的範圍。如果沒有出現括號,則將整個表示式視為一個選擇結構

  選擇項的嘗試匹配次序是從左到右,直到發現了匹配項,如果某個選擇項匹配就忽略右側其他選擇項,如果所有子選擇項都不匹配,則整個選擇結構匹配失敗

console.log(/12|23|34/.exec('1'));//null
console.log(/12|23|34/.exec('12'));//['12']
console.log(/12|23|34/.exec('23'));//['23']
console.log(/12|23|34/.exec('2334'));//['23']

  ip地址一般由3個點號和4段數字組成,每段數字都在0-255之間

(00)?\d; //0-9
0?[1-9]\d;//10-99
1\d{2};//100-199
2[0-4]\d;//200-249
25[0-5];//250-255
//數字(0-255)
/(00)?\d|0?[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]/;
//ip地址
var ip = /^(((00)?\d|0?[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}\2$/;
console.log(ip.test('1.1.1.1'));//true
console.log(ip.test('1.1.1'));//false
console.log(ip.test('256.1.1.1'));//false

  類似地,時間匹配也需要分段處理

//月(1-12)
0?\d|1[0-2]
//日(1-31)
0?\d|[12]\d|3[01]
//小時(0-24)
0?\d|1\d|2[0-4]
//分鐘(0-60)
0?\d|[1-5]\d|60

  手機號一般是11位,前3位是號段,後8位一般沒有限制。而且,在手機開頭很可能有0或+86

//開頭
(0|\+86)?
//前3位
13\d|14[579]|15[0-35-9]|17[0135-8]|18\d
//後8位
\d{8}

//手機號碼
var phone = /(0|\+86)?(13\d|14[579]|15[0-35-9]|17[0135-8]|18\d)\d{8}/;
console.log(phone.test('13453250661'));//true
console.log(phone.test('1913250661'));//false
console.log(phone.test('1345325061'));//false

  在選擇結構中,應該儘量避免選擇分支中存在重複匹配,因為這樣會大大增加回溯的計算量

//不良的選擇結構
a|[ab]
[0-9]|\w

 

斷言

  在正規表示式中,有些結構並不真正匹配文字,而只負責判斷在某個位置左/右側是否符合要求,這種結構被稱為斷言(assertion),也稱為錨點(anchor),常見的斷言有3種:單詞邊界、行開頭結尾、環視

單詞邊界

  在文字處理中可能會經常進行單詞替換,比如把row替換成line。但是,如果直接替換,不僅所有單詞row都被替換成line,單詞內部的row也會被替換成line。要想解決這個問題,必須有辦法確定單詞row,而不是字串row

  為了解決這類問題,正規表示式提供了專用的單詞邊界(word boundary),記為\b,它匹配的是'單詞邊界'位置,而不是字元。\b匹配的是一邊是單詞字元\w,一邊是非單詞字元\W的位置

  與\b對應的還有\B,表示非單詞邊界,但實際上\B很少使用

console.log(/\ban\b/.test('an apple'));//true
console.log(/\ban\b/.test('a an'));//true
console.log(/\ban\b/.test('an'));//true
console.log(/\ban\b/.test('and'));//false
console.log(/\ban\b/.test('ban'));//false

起始結束

  常見的斷言還有^和$,它們分別匹配字串的開始位置和結束位置,所以可以用來判斷整個字串能否由表示式匹配

//匹配第一個單詞
console.log(/^\w*/.exec('first word\nsecond word\nthird word'));//['first']
//匹配最後一個單詞
console.log(/\w*$/.exec('first word\nsecond word\nthird word'));//['word']
console.log(/^a$/.test('a\n'));//false
console.log(/^a$/.test('a'));//true

  ^和$的常用功能是刪除字串首尾多餘的空白,類似於字串String物件的trim()方法

function fnTrim(str){
    return str.replace(/^\s+|\s+$/,'')
}  
console.log(fnTrim('      hello world   '));//'hello world'

環視

  環視(look-around),可形象地解釋為停在原地,四處張望。環視類似於單詞邊界,在它旁邊的文字需要滿足某種條件,而且本身不匹配任何字元

  環視分為正序環視和逆序環視,而javascript只支援正序環視,相當於只支援向前看,不支援往回看

  而正序環視又分為肯定正序環視和否定正序環視

  肯定正序環視的記法是(?=n),表示前面必須是n才匹配;否定正序環視的記憶法是(?!n),表示前面必須不是n才匹配

console.log(/a(?=b)/.exec('abc'));//['a']
console.log(/a(?=b)/.exec('ac'));//null
console.log(/a(?!b)/.exec('abc'));//null
console.log(/a(?!b)/.exec('ac'));//['a']

console.log(/a(?=b)b/.exec('abc'));//['ab']

  [注意]環視雖然也用到括號,卻與捕獲型分組編號無關;但如果環視結構出現捕獲型括號,則會影響分組

console.log(/ab(?=cd)/.exec('abcd'));['ab']
console.log(/ab(?=(cd))/.exec('abcd'));['ab','cd']

 

匹配模式

  匹配模式(match mode)指匹配時使用的規則。設定特定的模式,可能會改變對正規表示式的識別。前面已經介紹過建立正規表示式物件時,可以設定'm'、'i'、'g'這三個標誌,分別對應多行模式、不區分大小模式和全域性模式三種

i

  預設地,正規表示式是區分大小寫的,通過設定標誌'i',可以忽略大小寫(ignore case)

console.log(/ab/.test('aB'));//false
console.log(/ab/i.test('aB'));//true

m

  預設地,正規表示式中的^和$匹配的是整個字串的起始位置和結束位置,而通過設定標誌'm',開啟多行模式,它們也能匹配字串內部某一行文字的起始位置和結束位置

console.log(/world$/.test('hello world\n')); //false
console.log(/world$/m.test('hello world\n')); //true

console.log(/^b/.test('a\nb')); //false
console.log(/^b/m.test('a\nb')); //true

g

  預設地,第一次匹配成功後,正則物件就停止向下匹配了。g修飾符表示全域性匹配(global),設定'g'標誌後,正則物件將匹配全部符合條件的結果,主要用於搜尋和替換

console.log('1a,2a,3a'.replace(/a/,'b'));//'1b,2a,3a'
console.log('1a,2a,3a'.replace(/a/g,'b'));//'1b,2b,3b'

 

優先順序

  正規表示式千變萬化,都是由之前介紹過的字元組、括號、量詞等基本結構組合而成的

//從上到下,優先順序逐漸降低
\                            轉義符
() (?!) (?=) []              括號、字元組、環視
* + ? {n} {n,} {n,m}         量詞
^ $                          起始結束位置
|                            選擇

  由於括號的用途之一就是為量詞限定作用範圍,所以優先順序比量詞高

console.log(/ab{2}/.test('abab'));//false
console.log(/(ab){2}/.test('abab'));//true

  [注意]選擇符'|'的優先順序最低,比起始和結束位置都要低

console.log(/^ab|cd$/.test('abc'));//true
console.log(/^(ab|cd)$/.test('abc'));//false

 

侷限性

  儘管javascript中的正規表示式功能比較完備,但與其他語言相比,缺少某些特性

  下面列出了javascript正規表示式不支援的特性

  【1】POSIX字元組(只支援普通字元組和排除型字元組)

  【2】Unicode支援(只支援單個Unicode字元)

  【3】匹配字串開始和結尾的\A和\Z錨(只支援^和$)

  【4】逆序環視(只支援順序環視)

  【5】命名分組(只支援0-9編號的捕獲組)

  【6】單行模式和註釋模式(只支援m、i、g)

  【7】模式作用範圍

  【8】純文字模式


參考資料

【1】 阮一峰Javascript標準參考教程——標準庫RegExp物件 http://javascript.ruanyifeng.com/stdlib/regexp.html
【2】《正則指引》
【3】《精通正規表示式》
【4】《javascript權威指南(第6版)》第10章 正規表示式的模式匹配
【5】《javascript高階程式設計(第3版)》第5章 引用型別
【6】《javascript語言精粹(修訂版)》第7章 正規表示式

 

相關文章