正則表達式


正則表達式是一個威力巨大的處理字符串的工具,能夠高效、神奇得完成對字符串的操作。相比較簡單的字符串比較、查找、替換,正則表達式提供了更加強大的處理能力。正則表達式的價值就在於,不用正則來解決問題會讓人瘋掉,但是用了之後“糾結”的問題已不再是問題了。而且因爲正則表達式用的不是一個固化的、具體的字符串來匹配字符串,而是抽象的模式的,所以只要正則寫的規則沒問題,一般都都能高效的完成任務。

雖然正則表達式看起來確實很像外星文,就像變魔術一樣,魔術本身也不神奇,只是的觀衆不解其中奧妙。學會了其中的規則,我們再去使用,肯定會發出感慨:神奇、複雜、好用。

正則到底強在那裏呢?我們舉個簡單的例子:在一串包含數字以及英文字母的字符串中中找出數字並保存在數組中。代碼如下:

  1. 不使用正則:

    遍歷字符串,利用字符串charAt()的方法將字符串中的數字檢索出來,再push數組中,然後繼續檢索再push到數組中直到結束。

        var str = '12 javascript 34 html5 33 php 77 css';
        var arr = [];
        var figure = '';
        for(var i=0;i='0' && str.charAt(i)<='9'){
                figure += str.charAt(i);
            }else{
                if(figure){
                arr.push(tmp);
                figure ='';
            }
        }
    }
    console.log(arr)//[ "12" , "34" , "5" , "33" , "77" ]
    不使用正則提取數字
  2. 使用正則:

    只需要 var arr = str.match(/\d+/g);這樣一句代碼。

看了以上的例子對比,是不是也覺得很神奇。那我們趕緊來學習神奇而好用的正則吧。

正則表達式基礎

javascript中的正則是Perl5的正則表達式語法的大子集,所以在javascript中創建正則有js風格和petl風格兩種。

  1. JS 風格: new RegExp('patten','ig')
  2. perl風格:/patten/ig

JS風格其實就是通過RegExp對象來表示,而perl風格更普遍的叫法是RegExp直接量。這兩個語法都是一樣的,只是轉義字符的寫法不同。

正則表達式的結構與數學表達式很類似。

爲了方便理解,讓我們先來看看大家一個典型的的數學表達式:(x+3)*2+y。一個數學表達式由若干個“項”組成,“項”與“項”之間用加號或減號相連;這裏“(x+3)*2”和“y”分別是兩個項。每個項又由若干個“因子”組成,因子之間用乘號或除號相連;這裏第一個項有兩個因子“(x+3)”和“2”,而第二個項只有一個因子“y”。每個因子可以是一個簡單的數,一個代數變量,也可以是放在括號裏面的另一個表達式,括號中的表達式稱爲“子表達式”。這裏“x+3”就是一個子表達式。

與數學表達式的“因子”相對應,構成正則表達式的部件稱爲“單位”;“項”則與正則表達式的自表達式相對應。而從邏輯上講,子表達式之間是串接的關係,一個字符串必須與每個子表達式依次相匹配,纔算與這個表達式相匹配。

如上例中斜槓後面的 “ig”是匹配模式,可選的值有3個:'i','g','m'。其含義如下:

  1. 'i':爲 ignore case,即 忽略大小寫。
  2. 'g':爲 global search,即全局搜索。
  3. 'm':爲 moltiline search,即多行搜索。

爲了更好的學習正則,我們再來學習下正則表達式的一些術語

  1. 匹配(matching)

    一個正則表達式“匹配”一個字符串,其實是指這個正則表達式能在字符串中找到匹配文本。

  2. 元字符(metacharacter)

    只有在字符組外部並且是在未轉義之前的情況下,纔是一個元字符。

  3. 子表達式(subexpression)

    子表達式指的一般是整個正則表達式中的一部分,通常是括號內的表達式,或者有|分隔的多選分支。子表達式由不可分割的單位組成。

    與多選分支不同的是,量詞作用的對象是他們之前緊鄰的子表達式。而如果量詞之前緊鄰的是一個括號保衛的自表達式,則不管其多麼複雜都被視爲一個單元。

一個完整是由一個個“子表達式”組成的,而“子表達式”則是由各種符號組成,上面我們知道,字符就是由“元字符”和“轉義字符”組成,按照功能有如下幾種:

轉義字符

什麼是轉義字符?在\後面加字符就可以轉義爲特殊字符。

例如: \n匹配一個換行符, \\匹配“\”。

預定義特殊字符

  1. \o:Nol字符。
  2. \t:水平製表符。
  3. \v:垂直製表符。
  4. \n:換行符。
  5. \r:回車符。
  6. \b:退格符。 只有出現在字符中才有效,即[](中括號)中

以上就是常用的一些直接量字符。

字符類

  1. [ ]:表示範圍,一個字符的集合,匹配該集合中的任意一個字符,例如 [abc]就可以匹配"css"中的c;

    如果上例前面加 ^元字符,形如[^asd],則表示匹配除了asd的其他字符;

    如果覺得匹配的字符太多,而且類型相似,則可以用-元字符表示,那麼上例就可以這麼寫[a-c]這麼寫,所以上例也可以這麼寫 [^a-d]/p>

  2. \w和\W:\w表示匹配任何ASCII字符組成的單詞,等價於[a-zA-Z0-9];\W表示匹配不是ASCII字符組成的單詞等價於[^a-zA-Z0-9]。
  3. \s和\S:\s匹配空白符,等價於[\t\n\x0B\f\r];\S則匹配非空白字符,等價於[^\t\n\x0B\f\r]。
  4. \d和\D:\d匹配數字字符,等價於[0-9];\D匹配數字字符,等價於[^0-9]
  5. .:javascript有點特殊,由於瀏覽器的解析引擎不同,.的匹配範圍也有所不同。
    1. IE8以下:

      .匹配所有除了換行符"/n"換行符之外的任意字符。等同於[^\n\r]

    2. IE9以上以及其他瀏覽器

      .匹配所有除了換行符"/n"換行符和回車符“\r”之外的任意字符。等同於[^\n\r]

      document.write(/./.test("\r") + "
      ");
      document.write(/./.test("\n") + "
      ");
      /*IE8以下輸出true false;IE9以上及其他瀏覽器輸出 false false*/
      .不同瀏覽器匹配結果

    所以兼容性,還是不要使用 .,使用其他等價的匹配方法。

學完以上的正在表達式語法,如果我們只能讓正則匹配字符一次,如果想多幾次,就得重複再寫一遍。如果要一個字符匹配500次的話,那豈不是要寫500次相同。所以我們接下來我們只需要一些代碼,就能處理這些數量關係。

量詞

首先我們得了解匹配量詞都是匹配優先的,簡單說就是匹配量詞的結果總是嘗試匹配儘可能多的字符,直到匹配上限爲止,然後爲了讓整個表達式匹配成功,就需要“釋放”之前優先匹配的字符,所以也被稱爲貪婪模式。

而既然有貪婪模式,則一定也有非貪婪模式。

對於貪婪模式和非貪婪模式影響的是被量詞修飾的子表達式的匹配行爲,既在貪婪模式下,在整個表達式匹配成功的前提下,儘可能多的匹配,而非貪婪模式在在整個表達式匹配成功的前提下,儘可能少的匹配。而且允許允許接下來的正則繼續匹配。

貪婪模式的量詞,也叫簡單量詞,如下:

  1. {n}:n是一個正整數,表示前一個子表達式匹配n次。例如: /o{2}/匹配兩次o,它可以匹配”footer“,但是不能匹配hot中的o。
  2. {n,}:n是一個正整數,表示前一個子表達式至少匹配n次。例如:/o{2,}/,它可以匹配“footer”,也可以匹配“fooooooooooter”。
  3. {n,m}:n、m都是正整數,表示至少匹配n次,至多m次。
  4. ?:等價於{0,1}
  5. +:等價於{1,}
  6. *:等價於{0,}

而在貪婪模式後加上 ?就變成了非貪婪模式。

貪婪模式和非貪婪模式

在上面提到的一個前提條件就是在整個表達式匹配成功,爲什麼要強調這個前提條件呢,看如下例子:

var pattern = 'aAaAaAb';
console.log(/a+/i.exec(pattern)); //aAaAaA
console.log(/a+?/i.exec(pattern)); //a
console.log(/a+b/i.exec(pattern)); //aAaAaAb
console.log(/a+?b/i.exec(pattern)); //aAaAaAb
貪婪模式與非貪婪模式區別事例

全部是在忽略大小寫的模式下:

  1. 第一個匹配結果解釋:採用貪婪模式,在匹配第一個“a”時,整個表達式匹配成功了,由於採用了貪婪模式,所以仍然向右匹配,向右再也沒有可以成功匹配的子字串,匹配結束,最終匹配結果爲“aAaAaA”
  2. 第二個匹配結果解釋:採用非貪婪模式,在匹配第一個“a”時,整個表達式匹配成功了,由於採用了貪婪模式,所以結束匹配,最終匹配結果爲“a。”
  3. 第三個匹配結果解釋:採用貪婪模式,所以a+仍然可以匹配到“aAaAaA”,但是由於後面的 b無法匹配成功,所以爲了讓整個表達式匹配成功,a+必須讓出前面的匹配內容,所以最終匹配結果爲“aAaAaAb”。
  4. 第四個匹配結果解釋:採用非貪婪模式,所以a+任然可以匹配到“a”,但是由於後面的 b無法匹配成功,所以爲了讓整個表達式匹配成功,a+必須繼續匹配後面的直到“b”,所以最終匹配結果跟採用貪婪模式的匹配結果一樣,也爲“aAaAaAb”。

所以,不管是貪婪模式還是非貪婪模式,都只有 在整個表達式匹配成功的前提下量詞才能影響字表達式的匹配行爲

匹配位置

前面說的量詞是修飾子字符串的重複次數,而匹配位置則是來表示子字符串的出現位置,匹配的只是一個位置,所以是零寬度的。

  1. ^:匹配文字的開頭。如果正則表達式的匹配模式設置爲','m'則也匹配每個換行符或者回車符之後的位置。
  2. $:匹配文字的開頭。如果正則表達式的匹配模式設置爲','m'則也匹配每個換行符或者回車符之前的位置。
  3. /b:匹配單詞邊界,不匹配任何字符。

    所謂的“單詞”,就是位於\w(ASCII單詞)和\W(非ASCII單詞)之間的邊界,或者位於ASCII單詞與字符串開始或者結尾的合法位置。所以\/bjava/b\不匹配“javascript is more than java”中的javascript中java而只匹配之後的單詞“java”。

    而因爲javascript只支持ASCII字符不支持Unicode的,所以在javascript這門語言中\w就可以等價於[a-zA-Z0-9],也因爲於此,javascript中\w是不包括中文已經其他Unicode碼特殊符號的,如下例子:

    var str = "html5_css3中文_h5$c3&漢字%";
    console.log(str.match(/\w+/g)); //"html5_css3" , "_h5" , "c3"
    console.log(str.match(/.\b./g));//"3中" , "文_" , "5$" , "3&"
    \w和\b邊界的區別

    第一個例子中\w+匹配了"html5_css3" , "_h5" , "c3"三個字符串,而其他的因爲javascript只能匹配ASCII碼的字符,所以除了字母、數字、“_“以及”$“的字符就都成單詞的邊界;而當使用.\b.(除了換行符之外的任意字符,.匹配了那些\w無法識別的Unicode碼字符)匹配時,我們又得到"3中" , "文_" , "5&" ,說明這個字符串中有4個分界點5個子字符串,分別在"3中" , "文_" , "5&"之間,而四個子字符串分別是"html5_css3","中文,"_h5","$c3","&漢字%"。

    所以,在處理一些字符串時,如果要使用\b得先確認是否還有ASCII碼的字符。

    注意:\b[]中表示退格。

分組

學習完以上的,應該會知道中括號用來限定字符類的範圍,大括號則用來指定重複的次數,而小括號除了限制多選項的範圍以及將若干字符組合爲一個單位讓量詞能影響這個單元。還有一個用途就是,小括號能”記住“它們匹配成功的文本,在正則表達式的後面引用前面“記住”的匹配文本,通過 \後加以爲或者多位數字來實現,也就是“反向引用”。

看實際例子吧:

//1分組+量詞
console.log(/(js){2}/.test("jsjs"));//true
//2分組+範圍
console.log(/[JL]script/.test("Lscript"));//true
//3反向引用
console.log(/([jJ])s\1/.test("jsJs"));//false
console.log(/([jJ])s\1/.test("jsjs"));//true
小括號的作用

例1和例2將括號內的若干字符組合爲一個單位。而例3因爲\1引用的是之前匹配成功的字符串,所以例三中\1就只能匹配”js“而不能匹配”Js“。

然後介紹第二個分組的符號|

與小括號不同,小括號內的是一個整體(獨立的子表達式),而|分割開的各分支是多選分支,即你可以選擇|前面的也可以選擇|後面的,如果有多個|隔開則是多選幾。如下:

var reg = /(html5|css3|js)!!/;
console.log(reg.test("html5!!"));//true
console.log(reg.test("css3!!"));//true
console.log(reg.test("js!!"));//true
候選實例

非捕獲性分組

對帶圓括號的子表達式的引用確實強大,但是既然能夠反向引用,正則引擎肯定是保存了括號內的一些信息。所以從效率角度來看,如果只是爲了分組而不反向引用的話就採取非捕獲性分組的方法。要創建一個非捕獲性分組,只要在捕獲性分組的左括號的後面緊跟一個問號與冒號就行了。

從字面意思來看:非捕獲分組能分組,但是不能捕獲各個組的信息。如下:

var pattern1 = "JS,HTML5,CSS";
console.log(pattern1.replace(/(\w+),(?:\w+)/, "$2,$1"));//$2,JS,CSS
非捕獲性分組

前瞻

前瞻也是屬於零寬斷言,說白了就是匹配位置的高級變體。前面我們說過的只是單純的開頭、結尾以及單詞的邊界,而前瞻的匹配則更加隨意,如下:

  1. (?=p):要求之後的字符必須與p匹配
  2. (?!p):要求之後的字符必須不與p匹配

如下實例:

var reg1 = /java(?!Scrit)/;
var reg2 = /java(?=Scrit)/;
console.log(reg1.test("javaScrit")); //false
console.log(reg1.test("javaB"));//true
console.log(reg2.test("javaScrit")); //true
console.log(reg2.test("javaB"));//false
前瞻

支持正則的方法

首先我們來看看支持正則的字符串方法和 RegExp 自身對象的方法吧。

支持正則表達式的 String 對象的方法

  1. 字符串搜索

    search()方法用於檢索字符串中指定的子字符串,返回匹配的字符的位置(0-~)。

    如果沒有找到匹配的字符,則返回-1;將忽略RegExp中的全局模式,只返回第一個匹配的位置。

    var pattern = "hello html5 js css";
    console.log(pattern.search(/Html5/i));//6
    正則search實例
  2. 字符串匹配

    match()方法可以返回匹配結果的數組,並且依賴於regexp的全局標誌g。如果沒有全局標誌g,則只匹配一次;如果有,則匹配多次直到結束,最後返回一個存有匹配匹配文本的數組。

    match()即不提供與子表達式匹配的文本的信息,也不聲明每個匹配子串的索引位置。如果您需要這些信息,可以使用RegExp.exec()

    var pattern="2012 years 12 month 20 is the end of the world";
    console.log(pattern.match(/\d+/g));//["2012","12","20"]
    正則match實例
  3. 字符串替代

    replace()方法用於替換字符串或者正則表達式匹配的子字符串,並且也依賴於regexp的全局標誌g。如果沒有全局標誌g,則只替換第一個匹配的子字符串;如果有,則替換所有匹配的子字符串。

    replace()的第二個參數可以是字符串,也可以是函數。如果是字符串,則由每個匹配的字符串替換,其中 $ 具有特殊的含義:

    1. $n:其中n表示1-99,表示匹配的子字符串中的第n個,n就是帶圓括號的子表達式的位置。
    2. $&:全部匹配的子字符串
    3. $`:匹配子串左側的文本
    4. $':匹配子串右側的文本
    var pattern1 = "JS,HTML5,CSS";
    var replace1 = pattern1.replace(/(\w{1,}),(\w+)/, "$2,$1");
    console.log(replace1);//HTML5,JS,CSS
    var replace2 = pattern1.replace(/(\w+),/g,"$1-");
    console.log(replace2);//JS-HTML5-CSS
    正則replace實例
  4. 字符串分割

    split()方法用於把一個字符串分割成字符串數組。該方法有兩個參數,第一個參數是指定分割的邊界;第二個參數是指定返回數組的長度,如果沒有則字符串之間的都會被分割。

    若使用 split("") 則會將單詞分割成字母

    var pattern = "HTML5 JS CSS";
    var sWord1 = pattern.split(" ");
    var sWord2 = pattern.split("");
    console.log(sWord1);//[ "HTML5" , "JS" , "CSS" ]
    console.log(sWord2);//[ "H" , "T" , "M" , "L" , "5" , " " , "J" , "S" , " " , "C" , "S" , "S"]
    正則split實例

RegExp 對象的方法

  1. test

    test()方法用於檢索要檢測的字符串是否存在,若含有與regExp相匹配的文本,則返回true,否則返回false

  2. exec

    exec()方法用於匹配字串,跟不是全局的match()方法很類似,但是它不僅能檢索字符串中指定的值,返回找到的值,還能確定其位置。 比match()強大。如果利用 exec() 的lastIndex屬性反覆調用同樣可以模擬match()全局檢索字符串的效果。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章