正則表達式(基於JavaScript)


正則表達式(英語:Regular Expression),描述了一種字符串匹配的模式(pattern)、規則。

正則表達式通常被用來檢索、替換那些符合某個模式(規則)的文本。給定一個正則表達式和另一個字符串,我們可以達到如下的目的:

  1. 給定的字符串是否符合正則表達式的過濾邏輯(稱作“匹配”)
  2. 可以通過正則表達式,從字符串中獲取我們想要的特定部分

正則圖形化工具:

  1. https://regexper.com/
  2. https://jex.im/regulex/

創建方式

  • 字面量法

    const reg = /\d/g; // g表示全局匹配。不加表示搜索到第一個停止。

  • 構造函數法

    • 字符串 + 修飾符:const reg= new RegExp('abc', 'g'); // 注意有兩個反斜線轉義
    • 正則表達式:const reg = new RegExp(/abc/g);
    • 正則表達式+修飾符:const reg = new RegExp(/abc/g, 'i'); // 等價於 /abc/i,第二個參數中的修飾符會覆蓋第一個參數中的修飾符

普通字符

普通字符,包括沒有顯式指定為元字符的所有可打印和不可打印字符(這包括所有大寫和小寫字母、所有數字、所有標點符號和一些其他符號),通常正則表達式里面使用的就是數字、英文字母、部分標點符號。

元字符

元字符,又被稱為特殊字符,是一些在正則表達式中約定用來表示特殊語義的字符。

預定義類

字符 等價類 含義
. [^\r\n] 除換行回車外的任意字符
\d [0-9] 任意數字字符
\D [^0-9] 非數字字符
\s [\r\n\t\x0B\f] 空白符
\S [^\r\n\t\x0B\f] 非空白符
\w [a-zA-Z_0-9] 單詞字符(字母數字下划線共63個字符)
\W [^a-zA-Z_0-9] 非單詞字符
\b 單詞邊界
\B 非單詞邊界

量詞,實現多個相同字符的匹配

字符 含義
? 0 | 1(至多出現一次)
+ >= 1(至少出現一次)
***** >= 0(任意次)
{n} 出現n次
{n,m} 出現n-m次
{n,} 至少出現n次
{0, n} 至多出現n次

位置邊界

  • 單詞邊界\b

    單詞是構成句子和文章的基本單位,一個常見的使用場景是把文章或句子中的特定單詞找出來。如:

    One is never too old to learn.

    我想找到to這個單詞,但是如果只是使用/to/這個正則,就會同時匹配到cattoo這兩處文本。這時候我們就需要使用邊界#則表達式\b,其中b是boundary的首字母,改寫成/\bto\b/,這樣就只會匹配到to這個單詞。

  • 字符串邊界
    • ^ 字符串開頭
    • $ 字符串結尾

其它元字符

|:或。比如/abc|def/ abcdef

^:但是在[]內是取反.如[^abc]表示匹配的是除了a、b、c以外的內容。

\:轉義

():分組

[]:匹配括號中的一類字符,比如[abc]則可以匹配a或b或c。也可以使用[a-z]來連接兩個字符表示從a到z的任意字符,這是閉區間,所以包括a和z,[]中也可以連寫。如[a-zA-Z]表示匹配所有大小寫字母。

修飾符

修飾符 含義
g global 全局搜索,默認是匹配到第一個就停止
i ignore case 忽略大小寫,默認大小寫敏感
m multiline 多行搜索。默認是單行搜索。
u unicode 用來處理4字節的UTF-16編碼字符。(ES 6新增)
y sticky 與g類似,也是全局匹配。但y確保匹配必須從剩余的第一個位置開始。(ES 6新增)
s 開啟dotAll, 解決/./無法匹配\n\r等分隔符(ES 9新增)

舉個栗子:

  • m
const aaa = `@123
	     @234
	     @345`;
aaa.replace(/^@\d/gm,'Q'); // 如果不加m則只有"@1"會被替換
  • u

ES 6的字符串擴展中新增了使用大括號表示 Unicode 字符,如果使用這種表示法,那么在正則表達式中必須加上u修飾符,才能識別其中的大括號,否則{}會被解讀為量詞:

/\u{61}/.test('a'); // false,表示必須要匹配61個u字符
/\u{61}/u.test('a'); // true
/\u{20BB7}/u.test('𠮷'); // true
  • y

y與g一樣都是全局匹配,但g只要剩余串中存在匹配就可,而y修飾符確保匹配必須從剩余的第一個位置開始匹配,這也就是“粘連(sticky)”的涵義:

var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;
s.replace(r1, 'X'); // "X_X_X"
s.replace(r2, 'X'); //"X_aa_a"

如何檢測正則表達式使用了u或y修飾符呢?

ES 6新增了unicodesticky屬性分別來檢測:

var r2 = /\u{61}/u;
r2.unicode; // true
var r = /a+/y;
r.sticky; // true

另外ES 6還新增了flags屬性用來查看正則表達式使用了哪些修飾符:

var r2 = /\u{61}/ugimy;
r2.flags; // "gimuy"
  • s
// dot  www.imooc.com  dotAll
const reg = /./
console.log(reg.test('5')) // true
console.log(reg.test('x')) // true
console.log(reg.test('\n')) // false
console.log(reg.test('\r')) // false
console.log(reg.test('\u{2028}')) // false
console.log(reg.test('\u{2029}')) // false

const reg = /./s
console.log(reg.test('5')) // true
console.log(reg.test('x')) // true
console.log(reg.test('\n')) // true
console.log(reg.test('\r')) // true
console.log(reg.test('\u{2028}')) // true
console.log(reg.test('\u{2029}')) // true

貪婪模式與非貪婪模式

  • 貪婪與非貪婪模式影響的是被量詞修飾的子表達式的匹配行為,貪婪模式在整個表達式匹配成功的前提下,盡可能多的匹配,而非貪婪模式在整個表達式匹配成功的前提下,盡可能少的匹配。

  • 從正則語法的角度來講,被匹配優先量詞修飾的子表達式使用的就是貪婪模式,如"(Expression)+";被忽略優先量詞修飾的子表達式使用的就是非貪婪模式,如"(Expression)+?"。

  • 屬於貪婪模式的量詞,也叫做匹配優先量詞

    • 包括:"{m,n}"、"{m,}"、"?"、"*"、"+"
    • 非貪婪模式用法:"{m,n}?"、"{m,}?"、"??"、"*?"、"+?"
  • 案例:

    // 思考下:`'12345678'.replace(/\d{3,6}/g, 'y');`
    // 結果是`yy78`還是`y78`呢?
    // 我們在瀏覽器控制台打印出是`y78`,所以**正則表達式是貪婪**的,它會盡可能多的去匹配!
    // 那想讓它盡可能少的匹配(非貪婪)咋辦,很簡單--在**量詞**后面加**?**就可以了:
    
    '12345678'.replace(/\d{3,6}?/g, 'y'); // yy78 // 非貪婪模式
    '12345678'.replace(/\d{3,6}/g, 'y'); // y78
    

前瞻與后顧

前瞻分正向和反向,同樣的后顧也分正向和反向。

名稱 表示 含義
正向前瞻(先行斷言) exp(?=assert) 匹配exp正則且滿足assert條件的
反向前瞻(先行否定斷言) exp(?!assert) 匹配exp正則且不滿足assert條件的
正向后顧(后行斷言) exp(?<=assert) es9 新增
反向后顧(后行否定斷言) exp(?<!assert) es9 新增
  • 正向前瞻:x只有在y前面才匹配,寫成/x(?=y)/

    • 只匹配在數字之前的單詞:
    • 'a2*34*()va23'.replace(/\w(?=\d)/g,'X')// "X2*X4*()vXX3"
  • 反向前瞻:x只有不在y前面才匹配,寫成/x(?!y)/

    • 只匹配不在數字之前的單詞:
    • 'a2*34*()va23'.replace(/\w(?!\d)/g,'X') // "aX*3X*()Xa2X"
  • 正向后顧

    •   const str = 'ecmascript'
        const result = str.match(/(?<=ecma)script/) // ["script", index: 4, input: "ecmascript", groups: undefined]
      
  • 反向后顧

    •   const str = 'ecmascript'
        const result = str.match(/(?<!ecma)script/) // null
      

分組

  1. 重復單個字符,非常簡單,直接在字符后賣弄加上限定符即可。

    • a+ 表示匹配1個或一個以上的a,a?表示匹配0個或1個a
  2. 但是我們如果要對多個字符進行重復怎么辦呢?此時我們就要用到分組,我們可以使用小括號"()"來指定要重復的子表達式,然后對這個子表達式進行重復。

    • /\d(abc)?/ 這個正則中(abc)?表示要匹配0個或1個abc ,此時這個包含括號的表達式就表示一個分組

捕獲組

  • 捕獲組可以通過從左到右計算其開括號來編號 ,這種分組才會暫存匹配到的串

    例如,在表達式(A)(B(C)) 中,存在三個這樣的組:

    1 (A)
    2 (B(C))
    3 (C)
  • "ABC".match(/(A)(B(C))/)

  • "ABC".replace(/(A)(B(C))/, "組1:$1 - 組2:$2 - 組3:$3") // "組1:A - 組2:BC - 組3:C"
    

具名捕獲組

const reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const result = reg.exec('2020-08-20')
const groups = result && result.groups

console.log(result, groups)

非捕獲組

  • 以 ?: 開頭的組是純的非捕獲 組,它不捕獲文本 。就是說,如果小括號中以?:號開頭,那么這個分組就不會捕獲文本,當然也不會有組的編號。

  • "ABC".replace(/(?:A)(?:B)(?:C)/, "組1:$1 - 組2:$2 - 組3:$3") // "組1:$1 - 組2:$2 - 組3:$3"
    
  • // 上節里面的前瞻也是非捕獲分組
    
    "ABC".replace(/A(?=BC)/, "$1") // "$1BC",因其未捕獲,所以$1沒有值,就直接按字符串字面值解析。
    

String和RegExp原型上與正則相關的方法

RegExp

  1. RegExp.prototype.test(str)

    • 檢測傳入的字符串是否和正則表達式相匹配,一般用test方法只是檢測是否有匹配,所以不建議加g
  2. RegExp.prototype.exec(str)

    • 在一個指定字符串中執行一個搜索匹配。返回一個結果數組或 null

    • var reg1 = /\d(\w)\d/;
      var str = '$2b3asd4u8989sjf8b4';
      var result1 = reg1.exec(str);
      console.log(result1); 
      // ["2b3", "b", index: 1, input: "$2b3asd4u8989sjf8b4", groups: undefined]
      
      // 多個分組
      var reg2 = /\d(\w)\d(\w)\d(\w)/;
      var result2 = reg2.exec(str);
      console.log(result2); 
      // ["4u8989", "u", "9", "9", index: 7, input: "$2b3asd4u8989sjf8b4", groups: undefined]
      
    • // 如果加了g或y修飾符,我們就可以循環獲取匹配到的字符
      var reg2= /\d(\w)\d/g; //全局搜索
      var str = '$2b3asd4u8989sjf8b4';
      var ret;
      while(ret = reg2.exec(str)) {
        console.log(`匹配到${ret[0]} ${ret.toString()} 下次查找從${reg2.lastIndex}開始`);
      }
      匹配到2b3 2b3,b 下次查找從4開始
      匹配到4u8 4u8,u 下次查找從10開始
      匹配到989 989,8 下次查找從13開始
      匹配到8b4 8b4,b 下次查找從19開始
      
    • 注意:不要把正則表達式字面量(或者RegExp構造器)放在 while 條件表達式里。由於每次迭代時 lastIndex 的屬性都被重置,如果匹配,將會造成一個死循環(一直匹配)。並且要確保使用了'g'標記來進行全局的匹配,否則同樣會造成死循環。

String

  1. String.prototype.search(regexp)

    • 注意:該方法是強制正則匹配模式,意思是傳入的參數不是正則會將其new RegExp(str)轉為正則對象。
      該方法用於檢索字符串中指定的子串,或檢索與正則表達式相匹配的子串,該方法只返回第一個匹配結果的index,查找不到返回-1。另外search方法不執行全局匹配,它將忽略標志g,並且總是從字符串的開始進行檢索。

    • var str = "123456789.abcde";
      console.log( str.search(".") );  // 0 因為new RegExp(".")匹配除回車換行外的任意字符
      console.log( str.search("\\.") );  // 9  相當於 new RegExp("\\.")
      console.log( str.indexOf("\\.") ); // -1 匹配字符 \. 所以不存在
      
  2. String.prototype.match(regexp)

    • 非全局匹配**該方法只執行一次匹配,沒有匹配到則返回null,若有匹配返回一個匹配文本相關的數組,返回的數組內容與非全局調用exec方法完全一樣。
  3. String.prototype.split([separator[, limit]])

    • 第一個參數是拆分點的字符串或正則,第二個表示返回的拆分元素限制。

      • 參數為字符串,不保留分隔符

        var myString = "a 123 b 123 c 123 d";
        var splits = myString.split("123");
        // ["a ", " b ", " c ", " d"] 結果中未保留分隔符
        
      • 參數為正則,假如有分組,結果中會保留分組

        var myString = "a 123 b 456 c 789 d";
        var splits = myString.split(/\d(\d)\d/);
        //   ["a ", "2", " b ", "5", " c ", "8", " d"]  結果中保留了分組內容
        
  4. String.protoype.replace(regexp|substr, newSubStr|function)

    • 一般來說,function有三個參數(如有分組則可能有【3+分組數】個參數),分別是匹配到的字符串、分組內容(若無分組則沒有該參數)、index、input(原字符串)
    • 內容比較多,詳情查看mdn


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM