JavaScript: 詳解正則表達式之三

在上兩篇文章中博主介紹了JavaScript中的正則經常使用方法正則修飾符,今天準備聊一聊元字符和高級匹配的相關內容。javascript

首先說說元字符,想必你們也都比較熟悉了,JS中的元字符有如下幾種:html

/ \ | . * + ? ^ $ ( ) [ ] { }

它們都表示特殊的含義,下面咱們就來一一介紹它們。java

/ (slash)

用於建立一個字面量的正則表達式:es6

var re = /abc/;

\ (backslash)

用於對其餘字符進行轉義,咱們稱其爲轉義字符,上面列舉的幾個元字符,因爲它們都表示特殊的含義,若是要匹配這些元字符自己,就須要轉義字符的幫忙了,好比咱們要匹配一個斜槓 / 的話,就須要像下面這樣:正則表達式

/\//.test('a/b');

| (vertical bar)

通常用於兩個多選分支中,表示「或」的關係,有了它,咱們就能匹配左右兩邊任意的子表達式了,下面例子匹配單詞see或sea:數組

/see|sea/.test('see');  // true

/see|sea/.test('see');  // true

. (dot)

匹配除換行符之外的任意字符,咱們可使用它來匹配幾乎全部的字母或字符,除了\r (\u000D carriage return)和\n (\u000A new line),看下面例子:ide

/./.test('w');      // true
/./.test('$');      // true

/./.test('\r');     // false
/./.test('\n');     // false

但須要注意的是,若是遇到碼點大於0xFFFF的Unicode字符,就不能識別了,必須加上u修飾符:ui

/^.$/.test('𠮷');   // false
/^.$/u.test('𠮷');  // true

* (asterisk)

用於匹配0到多個子表達式,也就是說,子表達式無關緊要,可多可少。若是咱們在單個字符後加上星號,它僅做爲這個字符的量詞,最終的匹配結果還與上下文有關,看下面例子:spa

/lo*/.test('hell');     // true
/lo*/.test('hello');    // true
/lo*/.test('hellooo');  // true

/lo*/.test('hey yo');   // false
/yo*/.test('hey yo');   // true

+ (plus)

用於匹配1到多個子表達式,也就是說,子表達式必須存在,至少連續出現1次。咱們還用上面的例子,結果會有所不一樣:code

/lo+/.test('hell');     // false
/lo+/.test('hello');    // true
/lo+/.test('hellooo');  // true

? (question mark)

用於匹配0到1個子表達式,也就是說,子表達式要麼不存在,要麼必須出現一次,不能連續出現屢次。咱們對上面的例子稍加改動:

/lo?$/.test('hell');     // true
/lo?$/.test('hello');    // true

/lo?$/.test('hellooo');  // false

^ (caret) & $ (dollar)

這兩個元字符分別用來限定起始和結束,咱們在上面的例子中也使用到了,這裏再舉一個簡單的示例:

/^hello/.test('hello');   // true
/world$/.test('world');   // true

/^hello/.test('hey yo');  // false
/world$/.test('word');    // false

我想大概不少人最初接觸這兩個元字符時,都寫過這樣的程序 - 去除字符串先後多餘空格:

var source = '  hello world  ';

var result = source.replace(/^\s+|\s+$/g, '');

console.log(result);

// output:
// "hello world"

( ) open parenthesis & close parenthesis

用於聲明一個捕獲組,括號中的子表達式將被匹配並記住,做爲捕獲組的內容,它們會從索引爲1的位置,出如今結果數組中:

/hel(lo)/.exec('hello');    // ["hello", "lo"]

/he(l(lo))/.exec('hello');  // ["hello", "llo", "lo"]

[ ] open bracket & close bracket

用於聲明一個字符集合,來匹配一個字符,這個字符能夠是集合中的任意一個,先看下面例子:

/[abc]/.test('b');    // true

咱們也能夠在其中兩個字符中間加入一個 - (hyphen) ,用於表示字符的範圍,下面例子效果與上面等同:

/[a-c]/.test('b');    // true

若是 - 出如今集合的首尾處,則再也不表示範圍,而是匹配一個實際的字符,以下所示:

/[-a]/.exec('-abc');  // ["-"]
/[c-]/.exec('-abc');  // ["-"]

從上面的例子中,咱們也能夠看到,集合中的字符會按順序優先匹配。除此以外,多個範圍也可同時出現,使整個集合有了更大的匹配範圍:

/[A-Za-z0-9_-]/.exec('hello');  // ["h"]

其中的"A-Za-z0-9_"能夠用"\w"表示,因此下面例子效果與上面等同:

/[\w-]/.exec('hello');          // ["h"]

最後,咱們還記得上面介紹的^嗎,在通常的表達式中,它表示起始標記,但若是出如今[]的起始位置,會表示一個否認,表示不會匹配集合中的字符,而是匹配除集合字符之外的任意一個字符:

/[^abc]/.test('b');   // false

/[^a-c]/.test('b');   // false

/[^a-c]/.test('d');   // true

{ } open brace & close brace

做爲子表達式的量詞,限定其出現的次數,有x{n},x{n,},x{n,m}幾種用法,下面分別舉例說明:

// o{3} o須出現3次

var re = /hello{3}$/;

re.test('hello');     // false

re.test('hellooo');   // true


// o{1,} o出現次數大於或等於1

var re = /hello{1,}$/;

re.test('hell');      // false

re.test('hello');     // true

re.test('hellooo');   // true


// o{1,3} o出現次數介於1和3之間,包括1和3

var re = /hello{1,3}$/;

re.test('hello');     // true

re.test('helloo');     // true

re.test('hellooo');   // true

re.test('hell');      // false

re.test('hellooooo'); // false

另外,咱們上面講到的*,+,?,他們都有與之對應的表示法:

*  0到屢次 至關於{0,}

+ 1到屢次 至關於{1,}

? 0或1次 至關於{0,1}

說完了上面的幾種元字符,再來簡單看一下幾個經常使用的轉義字符:

元字符部分先到這裏,下面咱們來聊聊正則高級匹配。

 

捕獲組引用

上面咱們也瞭解到,(x)是一個捕獲組,x是捕獲組中的子表達式,匹配到的捕獲組會從索引爲1處出如今結果數組中。這些匹配到的捕獲組,咱們可使用$1 ... $n表示:

// $1 ... $n 表示每個匹配的捕獲組

var re = /he(ll(o))/;

re.exec('helloworld');  // ["hello", "llo", "o"]

// "llo"   ->   $1
// "o"     ->   $2

在String#replace()方法中,咱們能夠直接使用$n這樣的變量:

// 在String#replace()中引用匹配到的捕獲組

var re = /(are)\s(you)/;
var source = 'are you ok';

var result = source.replace(re, '$2 $1');

console.log(result);

// output:
// "you are ok"

能夠看到,匹配到的are和you調換了位置,其中$1表示are,$2表示you。

在正則中,咱們還可使用\1 ... \n這樣的子表達式,來表示前面匹配到的捕獲組,咱們稱爲反向引用。\1會引用前面第一個匹配到的捕獲組,\2會引用第二個,依次類推。下面這個例子,咱們用來匹配一對p標籤的內容:

var re = /<(p)>.+<\/\1>/;

re.exec('<div><p>hello</p></div>');   // ["<p>hello</p>", "p"]

從結果集中能夠看到,咱們成功匹配到了p標籤的所有內容,而結果集中索引爲1的元素,正是(p)捕獲組匹配的內容。

 

非捕獲組

非捕獲組也能夠理解爲非記憶性捕獲組,匹配內容但不記住匹配的結果,也就是說,匹配到的內容不會出如今結果集中。

咱們用(?:x)的形式表示非捕獲組,下面例子演示了捕獲組和非捕獲組的不一樣之處:

// 普通捕獲組

var re = /he(llo)/;
re.exec('hello');    // ["hello", "llo"]

// 使用(?:llo)時 "llo"只匹配但不會出如今結果集中

var re = /he(?:llo)/;
re.exec('hello');   // ["hello"]

 

惰性模式

咱們上面元字符部分提到的幾個表達式,例如:

x*  x+  x?  x{n}  x{n,}  x{n,m}

他們默認狀況是貪婪模式,就是儘量的匹配更多的內容。好比下面的例子中,咱們想匹配第一個HTML標籤,因爲默認是貪婪模式,它會匹配整個字符串:

var re = /<.*>/;

re.exec('<p>hello</p>');   // ["<p>hello</p>"]

這並非咱們想要的結果,該怎麼辦呢?

咱們須要在這些表達式後面追加一個問號,表示惰性模式,讓正則匹配儘量少的內容,上面的幾個表達式將會變爲下面這樣:

x*?  x+?  x??  x{n}?  x{n,}?  x{n,m}?

稍微改一下上面的例子,咱們來看看結果如何:

var re = /<.*?>/;

re.exec('<p>hello</p>');   // ["<p>"]

 

斷言

所謂斷言,是在指定子表達式的前面或後面,將會出現某種規則的匹配,只有匹配了這個規則,子表達式纔會被匹配成功。斷言自己並不會被匹配到結果數組中。

JavaScript語言支持兩種斷言:

零寬度正預測先行斷言,表示爲 x(?=y) ,它斷言x後面會緊跟着y,只有這樣纔會匹配x。

零寬度負預測先行斷言,表示爲 x(?!y) ,它斷言x後面不是y,只有符合此條件纔會匹配x。

下面例子演示了這兩個條件相反的斷言:

// hello後面是world時 才匹配hello

var re = /hello(?=world)/;

re.exec('helloworld');        // ["hello"]

re.exec('hellojavascript');   // null

// 與上面結果相反 hello後面不是world是 才匹配hello

var re = /hello(?!world)/;

re.exec('helloworld');        // null

re.exec('hellojavascript');   // ["hello"]

在斷言的部分,咱們還可使用更具表達力的條件:

var re = /hello(?=world|javascript)/;

re.exec('helloworld');        // ["hello"]

re.exec('hellojavascript');   // ["hello"]


var re = /hello(?=\d{3,})/;

re.exec('hello33world');      // null

re.exec('hello333world');     // ["hello"]

以上就是斷言部分。關於正則表達式的內容也就先到這裏了,先後一共三篇文章,涵蓋了JavaScript正則的大部份內容,但願對同窗們會有幫助。

本文完。

 

參考資料:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions

http://es6.ruanyifeng.com/#docs/regex

相關文章
相關標籤/搜索