首頁  >  問答  >  主體

javascript - 正規表示式中 ?= 和 ?: 的差異到底在哪裡

直接上例子:每三個數字中間加逗號

"123456789".replace(/(\d{3})(?:[^$])/g, ",");
//"123,567,9"

"123456789".replace(/(\d{3})(?=[^$])/g, ",");
//"123,456,789"

再上一個之前論壇裡出現過的例子,也是每三個數字中間加逗號

先看看 (?=pattern) 的使用,下面这个是正确的:

function groupByCommas(n) {
  return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
console.log(groupByCommas(1234567));    //1,234,567

如果我们把 ?= 换成 ?: 的话:

function groupByCommas(n) {
  return n.toString().replace(/\B(?:(\d{3})+(?!\d))/g, ",");
}
console.log(groupByCommas(1234567));    //1,

兩者的概念不用多說,查到有回答說:區別在於?= 是正向肯定斷言,進行的匹配是不佔查詢長度的;而?: 是非獲取匹配,進行的匹配是佔據查詢長度的。

但是還是不是很理解這裡的查詢佔據長度的說法,對著例子解釋,難道是說第一個例子(?=[^$])匹配的是非結尾,所以123之後的非結尾的長度最小是1個字符,所以把4給一起替代了?那怎麼不直接取代到結尾呢?第二個例子(?=(\d{3}) (?!\d))匹配的是3或3的倍數個數字,直接配對到了結尾,所以把234567也直接取代了?所以我的理解肯定是不對的

理解的不是很透徹,歡迎各位對著例子來解答一下我的困惑,不勝感激!

天蓬老师天蓬老师2684 天前1069

全部回覆(5)我來回復

  • 怪我咯

    怪我咯2017-06-15 09:24:42

    1. "123456789".replace(/(d{3})(?:[^$])/g, "$1,");
      ()表示捕獲型括號,(?:)(?表示非捕獲型括號,所以第一個括號匹配的內容會放在$1中,第二個括號匹配的內容不會放在$2中。 d{3}表示連續三個數字,[^$]表示匹配一個字符,只要這個字符不是$符號,需要注意的是[]表示匹配裡面的任意一個字符,但是肯定是要有一個的,所以[]匹配出來的字符的長度肯定是1,不存在0的情況,另外在[$]裡面的$符號是沒有特殊含義的,就是$這個字符,而不是匹配字串的結尾。
      因為d{3}匹配三個字符,​​[^$]匹配一個字符,所以這個正則匹配4個字符;來看匹配過程,首先"1234"是滿足的,"123"匹配d {3},"4"配對[^$],此時$1="123",所以"1234"被替換成"123,"。然後從5開始下次匹配,類似的"5678"滿足條件,$1="567",所以"5678"被替換成"567,"。然後從9開始匹配,下面沒有匹配了,匹配結束,結果為"123,567,9"。

    2. "1234567".replace(/B(?:(d{3})+(?!d))/g, ",");
      B匹配非單字邊界,也是一個位置,沒有寬度,(d{3})+匹配3的倍數數字序列,且個數至少是3個,+是量詞,表示1到多次,預設是貪婪的,貪婪就是盡可能多的匹配,(?!d)表示這個位置後面不是數字。
      看例子,首先B不匹配行首,所以匹配的位置移動到"1"後面的位置,此時B匹配1後面的位置,然後"234", "567"匹配d{3} ,因為是貪婪匹配,所以(d{3})+匹配"234567",然後因為7後面是字符串的結尾了,所以滿足斷言(?!d)不是數字,所以整個正則的匹配結果是"234567",所以"234567"被替換成了","。 1不動,所以"1234567"變成了"1,"。

    3. "123456789".replace(/(d{3})(?=[^$])/g, "$1,");
      這個正規表示式不滿足"千分位加號"的需求,"123456789"只是個特例(位數剛好是3的倍數),換成"12345678"結果是"123,456,78"。

    回覆
    0
  • typecho

    typecho2017-06-15 09:24:42

    佔據或"消耗"的意思是說匹配的部分是否可被其他正則 (後面的斷言,或/ /g的下一次) 再匹配。如果"消耗"掉了就不能再配對了。

    "123456789".replace(/(\d{3})(?=[^$])/g, (m, ) => `{matched=${m} =${}}`);
    // => '{matched=123 =123}{matched=456 =456}789'
    // (?=) 第一次没有消耗掉4, 第二次会从456..开始
    
    "123456789".replace(/(\d{3})(?:[^$])/g, (m, ) => `{matched=${m} =${}}`);
    // => '{matched=1234 =123}{matched=5678 =567}9'
    // (?:) 第一次消耗掉4,第二次会从567..开始

    另外例1 [^$]是匹配一個非$的字符,和行末無關。

    回覆
    0
  • 巴扎黑

    巴扎黑2017-06-15 09:24:42

    (?=456)符合一個位置,這個位置後面跟了456
    例如123(?=456)會匹配123456中的123,而不會匹配123457中的123,不佔用後面的並不會被佔用掉。 123456 配對的是123456
    , 而123(?=456)456 同樣匹配123456 後面加了 同樣匹配123456 後面加了(56)。 正規中可以用括號改變優先權等,另外,對於加括號的部分,會從左到右分配遞增的分配一個編號,在後面可以用編號引用這一部分匹配到的文本。在JS

    replace

    裡,替換的部分可以用$1之類的引用這一部分的匹配。 例如(a)1
    會匹配兩個連續的a,([A-Z])1匹配兩個連續相同的大小字母,(A-Z)1([a-z])2匹配兩個連續的大小字母,後面跟兩個連續的小寫字母(大小寫字母可以不同)。 有時候,我們只想改變優先權,不想分配編號(很少用到),就用

    (?:)

    比如(a)(?:b)(c)12
    匹配abcac,但是(a)(b)(c)12匹配abcab. http://zonxin.github.io/post/...

    回覆
    0
  • 世界只因有你

    世界只因有你2017-06-15 09:24:42

    ()括住的是可以使用$1(到$9)去配對的

    如下面這個,可以使用$1去匹配(捕獲)到括號裡的匹配到的值,

    a = '123,456';
    a.match(/\d{3}(,)/)

    使用(?:)則是不捕獲這一個。就是不能透過$1,去取得括號所得到的值

    a.match(/\d{3}(?:,)/)

    上面兩個都是匹配出含,的值
    來看下面這個,(?=)是用來匹配,但是不出現在結果裡的。下面這個的結果就沒有,

    a.match(/\d{3}(?=,)/)

    三個程式碼的結果如圖

    這個寫法是很有用的,有些內容不希望出現在結果裡,但是不用它又匹配不全,就可以用這個了

    回覆
    0
  • 世界只因有你

    世界只因有你2017-06-15 09:24:42

    這2個功能毫不相干
    模式1pattern1(?=pattern2)正向肯定斷言;模式2(?:pattern3)非捕獲性分組

    1.模式1: pattern2 本身不參與匹配,對pattern1的匹配結果(ret1)進行斷言:字符串中 ret1之後的內容是否匹配pattern2?若是,則ret1為模式1匹配結果,若否,則ret1不是模式1匹配結果。當然,不符合pattern1,則不符合模式1

    舉例:

      var str1='abc',str2='acb';var reg=/a(?=b)/
      console.log(reg.test(str1),reg.test(str2)) //=>true false
      //因为reg.test(str1)为真,输出匹配结果
      console.log(str1.match(reg)[0])  //=>a

    2.模式2 主要用於區別捕獲性分組(pattern4),記為模式3

    在數學中小括號用於進行一次優先運算;而模式3 ,除了對代碼進行隔離,pattern4參與了匹
    配,且對pattern4的匹配結果進行了存儲

    對於模式2(?:pattern3)的非捕獲性分組,則表示不會對pattern3的結果進行存儲,但本身
    pattern3參與了匹配,主要用於對代碼進行隔離。也就是要表現()本來的意義,而()在正規
    表達式中有了捕獲性分組的定義,於是增加一個?:以示區別。這和轉義符有異曲同工的妙處。

    舉例

    var  str='abc'
    var  reg1=/(\w)(\w)(\w)/
    var  reg2=/(\w)(?:\w)(\w)/
    //捕获了3次,RegExp.,RegExp.,RegExp....依次存储捕获结果
    var ret1=str.match(reg1)
    console.log(RegExp.,RegExp.,RegExp.)//=>a b c
    var ret2=str.match(reg2)
    //捕获了2次
    console.log(RegExp.,RegExp.) //=>a c

    @luckness 對題主的內容進行了詳細解答,而我對題主標題進行了解答。

    回覆
    0
  • 取消回覆