【正規表示式系列】一些概念(字元組、捕獲組、非捕獲組)

撒網要見魚發表於2017-12-18

前言

本文介紹一些正則中的常用名詞以及對應概念,譬如字元組,捕獲組、非捕獲組、反向引用、轉義和s

大綱

  • 字元組
  • 捕獲組
  • 反向引用
  • 非捕獲組
  • .sS
  • 轉義

字元組

[]字元組表示在同一個位置可能出現的各種字元,也就是說它的匹配結果只能是一個字元,不能是多個

例如[hello]匹配的不是hello而是h或e或l或o

特點

  • 結果只會匹配一個字元
  • 內部特殊字元無需轉義 [ ] 除外

    • 另外,^出現在最開始位置時需要轉義
    • -前後構成區間範圍時需要轉義(推薦永遠使用轉義-
  • -表示連字元
  • ^表示排除符

示例

特殊字元無需轉義

[^*-+|(a)]

這個示例的含義是,匹配以下字元中的任意一個

  • ^ * - + | ( a )
  • 可以看到,這些特殊字元在字元組中僅僅就是字元本身

連字元的作用

[z-a]

匹配這個正規表示式會報錯(Range out of order in character class

原因是連字元後面的字元碼要大於等於前面的字元碼

var str = `abc-`;
var reg = /[a-z-]/g;

// 最後的連字元沒有構成區間,所以僅僅表示  - 這個字元
str.match(reg); // ["a", "b", "c", "-"]
var str = "aAbc-";
var reg = /[A-a-z]/g;

// 通用,A-a 構成一個區間  之後的 -z 無法構成區間,於是它只能作為-字元本身
str.match(reg); // ["a", "A", "-"]

排除型

var str = `01ABabc-`;
var reg = /[^a-z]/g;

// 匹配了除a-z範圍外的任意字元
str.match(reg); // ["0", "1", "A", "B", "-"]

排除型字元組用法和普通的一樣,但是唯一區別是

  • 排除型代表符合條件的都不匹配(功能相反)

特殊情況

var str = `abd,cdef`;
var reg = /[]/g;
var reg2 =  /c/g; 
var reg3 =  /[]c/g; 

str.match(reg); // [""] 匹配本身
str.match(reg2); // ["c"]  即匹配,號後面的c
str.match(reg3); // null 因為後面沒有c
  • 在字元組以外表示單詞邊界
  • 在字元組內,表示退格符()

捕獲組

捕獲組就是把正規表示式中子表示式匹配的內容,儲存到記憶體中以數字編號或顯式命名的組裡,方便後面引用

例如: /(a)(b)(c)/中的捕獲組編號為

  • 0: abc
  • 1: a
  • 2: b
  • 3: c

其中,組0是正規表示式整體匹配結果,組1`2`3才是子表示式匹配結果

特點

  • 子表示式捕獲組編號從1開始,順序從左到右(例如編號1是左側第一個()包裹的子表示式的結果)
  • 可以在正規表示式中對前面捕獲的內容進行引用(反向引用)
  • 也可以在程式中,對捕獲組捕獲的內容進行引用(比如replace中)

示例

子表示式捕獲組編號從1開始,順序從左到右

var str = "2017-07-29";
var reg = /(d{4})-(d{2})-(d{2})/;

// 非全域性模式有捕獲組結果
str.match(reg); // ["2017-07-29", "2017", "07", "29", index: 0, input: "2017-07-29"]

上述匹配中,除了整個正規表示式的結果外,還有各個捕獲組的結果,其中,子表示式的捕獲組從編號1開始,如下

編號 捕獲組 匹配內容
0 (d{4})-(d{2})-(d{2}) 2017-07-29
1 d{4} 2017
2 d{2} 07
3 d{2} 29

JS程式內的引用

  • replace中,JS通過$number引用捕獲組內容
  • 在外部匹配引用,JS通過RegExp.$number引用捕獲組內容
var str = `<div id="code1" class="highlight"></div>`;
var reg = /<(w+)[^>]*>/g;

// <div></div>
str = str.replace(reg, "<$1>");

可以看到,在去除div中的屬性時,先是整個匹配<divxxx>,然後再把整個內容替換成<$1>,其中$1就是第一個捕獲組結果div的引用

注,請不要引用$0,因為它不屬於子表示式的捕獲組,在replace中引用$0沒有任何效果

var str = `abcd0123ABCD`;
var reg = /([a-z]+)(d+)([A-Z]+)/g;

reg.test(str); // true

console.log(RegExp.$0); // undefined
console.log(RegExp.$1); // abcd
console.log(RegExp.$2); // 0123
console.log(RegExp.$3); // ABCD

注,同樣$0的引用沒有內容

反向引用

在正規表示式內部對捕獲組進行引用稱之為反向引用

var str = "boom==boom";
var reg = /(boom)==1/;

str.match(reg); // ["boom==boom", "boom", index: 0, input: "boom==boom"]

可以看到,正則中1的值就是捕獲組1匹配到的結果boom

因此,這個表示式等價於(boom)==boom

示例

var str = `1234567899`;
var str2 = `12345678999`;
    
var reg = /^(?:([0-9])(?!1{2})){1,}$/;

reg.test(str); // true
reg.test(str2); // false

上例中的效果是,匹配一個數字,但是數字中不允許連續出現3次以上的重複數字

  • 用到反向引用可以很好的實現

非捕獲組

上述可以看到()包括的內容預設匹配時都在捕獲組中

但是有時候,因為特殊原因用到了(),但又沒有引用它的必要,這時候就可以用非捕獲組宣告,防止它作為捕獲組,降低記憶體浪費

  • ?:可以宣告一個子表示式為非捕獲組
var str = `abcd0123ABCD`;
var reg = /(?:[a-z]+)(d+)([A-Z]+)/g;

reg.test(str); // true

console.log(RegExp.$0); // undefined
console.log(RegExp.$1); // 0123
console.log(RegExp.$2); // ABCD

可以看到,(?:[a-z]+)將這個子表示式宣告成了非捕獲組,因此捕獲組的編號直接跳過了它,從下一個(d+)開始

.sS

首先說下.

  • 定義是除
    以外的任何字元
  • 但是,在一些ChromeFirefox等核心中,代表

    以外的字元
  • 如果要匹配.本身,請用.

再說說sS

  • s是匹配所有的空白字元,包括空白換行tab縮排等所有空白
  • S是指除了空白以外的任何字元(和.區別下,.裡面還多了一部分空白)

那如何匹配所有字元呢?

  • (.|
    )
    或者是[sS](推薦用法)
  • 請不要試圖使用[.
    ]
    [.
    ]
    ,這種寫法只表示小數點或
    字元中的一個

示例

請寫一個表示式,去除多行註釋

var str = `
    var a = 1; 

    /** 

    * 這裡是註釋 

    */

    var b = 2;

    console.log(a)`;

var reg = xxx;

str = str.replace(reg, ``);

解答

var reg = //*{1,}[sS]**//g;

這裡就用的了用[sS]來匹配所有的字元(因為僅僅是.是無法匹配
的)

單詞邊界

匹配單詞邊界,不匹配任何字元

簡單的說,匹配的位置,一側是構成單詞的字元,另一側是非單詞字元,字串的開始或結束

而其中單詞的判斷就是w的匹配範圍(正常a-zA-Z0-9_JS舉例)

注,有一特例,在字元組中[]表示的是退格符

特點

  • 零寬,即匹配的是位置而不是字元
  • w來界定單詞
  • 字元組中是退格符的意思

示例

var str = `abc_d=efg+hij哈opq%`;
    
var reg = /../g;

// ["d=", "g+", "j哈", "q%"]
str.match(reg);

可以看到,分別在如下幾處有單詞分界

  • d=直接有一個分界
  • g+之間
  • j
  • q%

轉義

var str = `ab\cd`; // 字串 abcd
var str2 = `ab\\cd`; // 字串 ab\cd
    
var reg = /ab\\cd/;
var reg2 = new RegExp(`ab\\cd`);

reg.test(str); // false
reg.test(str2); // true

reg2.test(str); // true
reg2.test(str2); // false

出現的地方,得多加註意,需要梳理清楚轉義邏輯,通過上例可以看到,在正則中出現的和在字串中出現的意義不一樣

  • reg中,出現在正則中,所以\\的意思就是匹配\字串,所以測試str2通過,str失敗
  • reg2中,出現在字串中,所以\\的意思\,然後構建為正規表示式,最終在正則中是\,也就是匹配字串本身,所以測試str通過,str2失敗

附錄

部落格

初次釋出2017.07.31於個人部落格

http://www.dailichun.com/2017/08/01/regularExpressionConcepts.html

參考資料

相關文章