1. 程式人生 > >js正則表示式中關於零寬斷言的奇異現象

js正則表示式中關於零寬斷言的奇異現象

碰到一個特別的需求,就是有一段Sql Server 的 SQL片段,內容大概就是所有JOIN表的集合,要求把這個SQL片段分割成陣列,每個元素就是包含單個表的字串。

例如:

SQL = INNER JOIN Sale b ON 1=1 LEFT JOIN OutStock c ON 1=1 RIGHT JOIN BunissMan c ON 1=1 

分割成:

["INNER JOIN Sale b ON 1=1 ","LEFT JOIN OutStock c ON 1=1 ","RIGHT JOIN BunissMan c ON 1=1 "]

還好理解吧?


實現的方式其實挺多,迴圈遍歷分割、正則分割等等。

為了感覺高大上一點,決定了正則。

就是在實驗的過程中,發現了一個奇異的現象。

本來想測試一下各種JOIN的匹配情況,於是採用正則表示式如下:

/(?=INNER)|(?=LEFT)|(?=RIGHT)|(?=FULL OUTER) JOIN/

採用match,沒有匹配到我想要的東西,但是,split卻出來了結果:

測試原始碼:

var SQL = 'INNER JOIN Sale b ON 1=1 LEFT JOIN OutStock c ON 1=1 RIGHT JOIN BusniessMan c ON 1=1 ';
var RegExpStr = /(?=INNER)|(?=LEFT)|(?=RIGHT)|(?=FULL OUTER) JOIN/;
var MatchAll = SQL.match(RegExpStr)
console.log(MatchAll)
var SplitArr = SQL.split(RegExpStr)
console.log(SplitArr)

測試結果:

["", index: 0, input: "INNER JOIN Sale b ON 1=1 LEFT JOIN OutStock c ON 1=1 RIGHT JOIN BusniessMan c ON 1=1 "] 
["INNER JOIN Sale b ON 1=1 ", "LEFT JOIN OutStock c ON 1=1 ", "RIGHT JOIN BusniessMan c ON 1=1 "]

著實讓我又驚又喜。

喜的是,這個需求這麼容易結束了。

驚的是,不知道這是怎麼得到的結果。

在python裡面測試了相同的零寬斷言,發現是不能進行分割的。


本著研究的精神繼續探索。

var SomeStr = '12345129345'
console.log(SomeStr.split(/234/))             // ["1", "5129345"]
console.log(SomeStr.split(/2(?=3)/))          // ["1", "345129345"]
console.log(SomeStr.split(/(?=2)34/))         // ["12345129345"]
console.log(SomeStr.split(/(?=2)234/))        // ["1", "5129345"]
console.log(SomeStr.split(/(?=2)|(?=9)3/))    // ["1", "23451", "29345"]
console.log(SomeStr.match(/(2|9)3/g))         // ["23", "93"]
console.log(SomeStr.match(/(?=23)|(?=93)/g))  // ["", ""]
console.log(SomeStr.split(/(?=23)|(?=93)/))   // ["1", "234512", "9345"]


分析一下:

第一行將234作為分隔符分割了,很好理解。

第二行使用了零寬斷言,能匹配2後面緊接著是3的的“分隔符”,所以將前面的“2”作為分隔符,而後面的2接著9,不能分隔。

第三行一開始困惑了我,理論上應該能將前面的234分割了呀,實際上不是的,"(?=2)3"這個正則匹配斷言所在位置為2,但是後面卻是3,所以永遠匹配不到。

第四行解釋了第三行的問題,斷言當前所在位置為2,因此能匹配到234這個字串。

第五行看懂了沒,根據上面3、4兩行的經驗,這裡不是表示能匹配23和93,而是僅僅能匹配 “2和一個空字串(並且不包含2)” 這個結果,93是永遠匹配不到的,因為(?=9)後面必須接9而斷言後面卻接了3;並且豎線“或關係”也不是按照我們頭腦中思考的分成 “(?=2)3 or (?=9)3” 而是 “(?=2) or (?=9)3” ;所以只能匹配前者 "(?=2)"。

第六行指示了正確使用豎線的方式。如果是 /2|93/ ,那麼結果就是2,2,93。

第七行和第八行展示了我們正確使用零寬斷言分割的方法。


所以,總結來說,並非是bug,而只是我不太理解零寬斷言而已,並且這裡很容易跳進坑裡面。

By the way,這麼弄來,我需求實現的程式碼錯了,應該這麼寫:

var SQL = 'INNER JOIN Sale b ON 1=1 LEFT JOIN OutStock c ON 1=1 RIGHT JOIN BusniessMan c ON 1=1 ';
var RegExpStr = /(?=INNER JOIN)|(?=LEFT JOIN)|(?=RIGHT JOIN)|(?=FULL OUTER JOIN)/;
var SplitArr = SQL.split(RegExpStr)
console.log(SplitArr)

——所以,還是有必要求個甚解。