Stories

Detail Return Return

javascript 正則 高級用法

先簡單看幾個常用基礎標識符

^ 匹配一個輸入或一行的開頭,

  /^a/
  // 匹配"an A",而不匹配"An a"

$ 匹配一個輸入或一行的結尾

/a$/
// 匹配"An a",而不匹配"an A"

*匹配前面元字符0次或多次

/ba*/
// 匹配b,ba,baa,baaa,...

+匹配前面元字符1次或多次

/ba+/
// 匹配ba,baa,baaa,...

? 匹配前面元字符0次或1次

/ba?/
// 匹配b,ba
(x) //匹配x保存x在名為$1...$9的變量中
x|y //匹配x或y
{n} //精確匹配n次
{n,} //匹配n次以上
{n,m} //匹配n-m次
[xyz] //字符集,匹配這個集合中的任一一個字符(或元字符),匹配x,y,z
[^xyz] //不匹配這個集合中的任何一個字符

正則表達式(Regular Expression)其實是一門工具,通過字符串模式匹配,實現搜索和替換功能。

它起源於20世紀50年代科學家在數學領域做的一些研究工作,後來才被引入到計算機領域中。

從它的命名我們可以知道,它是一種用來描述規則的表達式。而它的底層原理也十分簡單,就是使用狀態機的思想進行模式匹配。

這裏先不細糾概念,常用的方法屬性,文檔mdn什麼都有,也不糾結,直奔主題。

子表達式

子表達式(Reg)具有獨立的匹配功能,保存獨立的匹配結果

  1. 作為獨立單元可以使用*+?{n,m}等量詞

    /(ab)?c)/ 
    // 匹配c或者abc
  2. 作為子模式可以獨立處理,並且保留匹配結果子串,可通過RegExp.$1,...$n訪問

    var re = /(\w+)\s(\w+)/;
    var str = "John Smith";
    var newstr = str.replace(re, "$2, $1");
    console.log(newstr); // Smith, John

    以下RegExp屬性,不是w3c標準,不過大部瀏覽器支持

    RegExp屬性 描述
    $n 第 n 個子表達式匹配的字符串,只有1-9
    $& 最後匹配到的字符串,RegExp.lastMatch別名
    $' 最新匹配的右側子串,RegExp.rightContext 別名
    $` 最新匹配的左側子串,RegExp.leftContext別名
    $+ 匹配到的最後一個子串,RegExp.lastParen別名
    $_ 被匹配成功的原字符串,RegExp.input別名

    對應replace函數中訪問正則結果的參數

    變量名 描述
    $n 插入第 n 個子表達式匹配的字符串,只有1-99
    $& 插入匹配的子串
    $' 插入當前匹配的子串右邊的內容
    $` 插入當前匹配的子串左邊的內容
    $<name> 匹配[Name]具名子表達式的字符串
  3. 回溯引用(反向引用),模式的後面部分引用前面子表達式已經匹配到的子字符串
    通過反斜槓\加數字來實現的。數字代表子表達式在該正則表達式中的順序。例如: \1 引用的是第一個子表達式的匹配結果

    匹配結果不是匹配模式
    var s = "<h1>title<h1><p>text<p>";
    var r = /(<\/?\w+>).*\1/g;
    // 相當於/(<\/?\w+>).*(<h1>|<p>)/g
    // 再加(<\/?\w+>)匹配結果 === (<h1>|<p>)匹配結果
    var a = s.match(r);  //返回數組["<h1>title<h1>","<p>text<p>"]

子表達式的高級模式

非捕獲模式,匹配結果不會保留
/(?:\w+)\s(\w+)/
// ?:標識非捕獲
// $1 從第二個子表達式開始計算
命名捕獲,這個用的較少
var re = /(?<myName>\w+)\s(\w+)/;
console.log("John Smith".match(re));
// (?<Name>x)
// 匹配結果保存在匹配項的 groups 屬性中

1672889918820.png

斷言

斷言用來限制正則匹配的邊界。

其實^,&,\b,\B也是斷言。

  • ^ 對應字符串開頭
  • & 字符串結尾
  • \b 單詞邊界
  • \B 非單詞邊界

這裏主要説其他四種斷言

  1. 先行斷言,/x(?=y)/ y在後面跟隨x的時候匹配x
  2. 先行否定斷言,/x(?!y)/ y沒有在後面跟隨x的時候匹配x
  3. 後行斷言,/(?<=y)x/ y在x前面緊隨的時候匹配x
  4. 後行否定斷言,/(?<!y)x/ y沒有在x前面緊隨的時候匹配x

    // 先行斷言
    let regex = /First(?= test)/g;
    console.log('First test'.match(regex)); // [ 'First' ]
    
    // 先行否定斷言
    // /\d+(?!\.)/ 匹配沒有被小數點跟隨且至少有一位的數字。 /\d+(?!\.)/.exec('3.141') 匹配 "141" 而不是 "3"
    console.log(/\d+(?!\.)/g.exec('3.141')); // [ '141', index: 2, input: '3.141' ]
    
    // abc後面不能跟隨de
    let reg = /abc(?!de)/;
    reg.test('abcdefg');  // false;
    reg.test('abcd');  // true;
    reg.test('abcabc');   // true;

注意:匹配結果是不包括y的

正則表達式的三種模式

在使用修飾匹配次數的特殊符號時,有幾種表示方法可以使同一個表達式能夠匹配不同的次數,比如:"{m,n}", "{m,}", "?", "*", "+",具體匹配的次數隨被匹配的字符串而定。

  1. 貪婪模式
    貪婪模式總是儘可能多的匹配

    var regex = /\d{2,5}/g;
    var string = "123 1234 12345 123456";
    console.log( string.match(regex) );
    // => ["123", "1234", "12345", "12345"]
  2. 懶惰模式
    在修飾匹配次數的特殊符號後再加上一個 "?" 號,則可以使匹配次數不定的表達式儘可能少的匹配,使可匹配可不匹配的表達式,儘可能的 "不匹配"

    var regex = /\d{2,5}?/g;
    var string = "123 1234 12345 123456";
    console.log( string.match(regex) );
    // => ["12", "12", "34", "12", "34", "12", "34", "56"]
    其中 /\d{2,5}?/ 表示,雖然 2 到 5 次都行,當 2 個就夠的時候,就不再往下匹配
  3. 獨佔模式(js不支持)
    如果在表達式後加上一個加號(+),則會開啓獨佔模式。同貪婪模式一樣,獨佔模式一樣會匹配最長。不過在獨佔模式下,正則表達式儘可能長地去匹配字符串,一旦匹配不成功就會結束匹配而不會回溯。

正則的性能

正則引擎主要的兩大類:一種是DFA(確定型有窮自動機),另一種是NFA(不確定型有窮自動機)。NFA 對應正則表達式主導的匹配,DFA 對應文本主導的匹配。

DFA從匹配文本入手,從左到右,每個字符不會匹配兩次,它的時間複雜度是多項式的,所以通常情況下,它的速度更快,但支持的特性很少,不支持捕獲組、各種引用等等;

NFA則是從正則表達式入手,不斷讀入字符,嘗試是否匹配當前正則,不匹配則吐出字符重新嘗試,通常它的速度比較慢,最優時間複雜度為多項式,最差情況為指數級

NFA支持更多的特性,因而絕大多數編程場景下(包括java,js),我們面對的是NFA。

吐出字符重新嘗試就是回溯

正則表達式回溯法原理

user avatar
0 users favorite the story!

Post Comments

Some HTML is okay.