先簡單看幾個常用基礎標識符
^ 匹配一個輸入或一行的開頭,
/^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)具有獨立的匹配功能,保存獨立的匹配結果
-
作為獨立單元可以使用*+?{n,m}等量詞
/(ab)?c)/ // 匹配c或者abc -
作為子模式可以獨立處理,並且保留匹配結果子串,可通過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]具名子表達式的字符串 -
回溯引用(反向引用),模式的後面部分引用前面子表達式已經匹配到的子字符串
通過反斜槓\加數字來實現的。數字代表子表達式在該正則表達式中的順序。例如: \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 屬性中
斷言
斷言用來限制正則匹配的邊界。
其實^,&,\b,\B也是斷言。
- ^ 對應字符串開頭
- & 字符串結尾
- \b 單詞邊界
- \B 非單詞邊界
這裏主要説其他四種斷言
- 先行斷言,/x(?=y)/ y在後面跟隨x的時候匹配x
- 先行否定斷言,/x(?!y)/ y沒有在後面跟隨x的時候匹配x
- 後行斷言,/(?<=y)x/ y在x前面緊隨的時候匹配x
-
後行否定斷言,/(?<!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,}", "?", "*", "+",具體匹配的次數隨被匹配的字符串而定。
-
貪婪模式
貪婪模式總是儘可能多的匹配var regex = /\d{2,5}/g; var string = "123 1234 12345 123456"; console.log( string.match(regex) ); // => ["123", "1234", "12345", "12345"] -
懶惰模式
在修飾匹配次數的特殊符號後再加上一個 "?" 號,則可以使匹配次數不定的表達式儘可能少的匹配,使可匹配可不匹配的表達式,儘可能的 "不匹配"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 個就夠的時候,就不再往下匹配 - 獨佔模式(js不支持)
如果在表達式後加上一個加號(+),則會開啓獨佔模式。同貪婪模式一樣,獨佔模式一樣會匹配最長。不過在獨佔模式下,正則表達式儘可能長地去匹配字符串,一旦匹配不成功就會結束匹配而不會回溯。
正則的性能
正則引擎主要的兩大類:一種是DFA(確定型有窮自動機),另一種是NFA(不確定型有窮自動機)。NFA 對應正則表達式主導的匹配,DFA 對應文本主導的匹配。
DFA從匹配文本入手,從左到右,每個字符不會匹配兩次,它的時間複雜度是多項式的,所以通常情況下,它的速度更快,但支持的特性很少,不支持捕獲組、各種引用等等;
NFA則是從正則表達式入手,不斷讀入字符,嘗試是否匹配當前正則,不匹配則吐出字符重新嘗試,通常它的速度比較慢,最優時間複雜度為多項式,最差情況為指數級。
NFA支持更多的特性,因而絕大多數編程場景下(包括java,js),我們面對的是NFA。
吐出字符重新嘗試就是回溯。
正則表達式回溯法原理