目前,您能夠經過幾種方式獲取給定正則表達式的全部匹配項。javascript
若是正則表達式有/g標誌,那麼屢次調用.exec()就會獲得全部匹配的結果。若是沒有匹配的結果,.exec()就會返回null。在這以前會返回每一個匹配的匹配對象。這個對象包含捕獲的子字符串和更多信息。php
舉個例子:獲得全部雙引號之間的字符串java
function collectGroup1(regExp, str) { const matches = []; while (true) { const match = regExp.exec(str); if (match === null) break; // 把match中捕獲的字符串,加到matches中。 matches.push(match[1]); } return matches; } // /"([^"]*)"/ug 匹配全部雙引號與其之間的內容,並捕獲全部引號間的信息。 collectGroup1(/"([^"]*)"/ug,`"foo" and "bar" and "baz"`); // [ 'foo', 'bar', 'baz' ]
若是正則表達式沒有/g標誌,.exec()老是返回第一次匹配的結果。正則表達式
> let re = /[abc]/; > re.exec('abc') [ 'a', index: 0, input: 'abc' ] > re.exec('abc') [ 'a', index: 0, input: 'abc' ]
這樣的話對函數collectGroup1就是一個壞消息,由於若是沒有/g標誌,函數沒法結束運行,此時match就一直是第一次匹配的結果,循環永遠沒法break。數組
爲何會這樣?ruby
由於正則表達式有一個lastIndex(初始值爲0)屬性,每次.exec()前,都會根據lastIndex屬性的值來決定開始匹配的位置。函數
若是正則表達式沒有/g標誌,那麼運行一次.exec()時,不會改變lastIndex的值,致使下一次運行exec()時,匹配仍舊是從字符串0的位置開始。ui
當正則表達式加了/g標誌後,運行一次exec(),正則表達式的lastIndex就會改變,下次運行exec()就會從前一次的結果以後開始匹配。spa
你可使用.match()方法和一個帶有/g標誌的正則表達式,你就能夠獲得一個數組,包含全部匹配的結果(換句話說,全部捕獲組都將被忽略)。prototype
> "abab".match(/a/ug) [ 'a', 'a' ]
若是/g標誌沒有被設置,那麼.match()與RegExp.prototype.exec()返回的結果同樣。
> "abab".match(/a/u) [ 'a', index: 0, input: 'abab' ]
你能夠用一個小技巧來收集全部的捕獲組——經過.replace()。replace函數接收一個可以返回要替換的值的函數,這個函數可以接收全部的捕獲信息。可是,咱們不用這個函數去計算替換的值,而是在函數裏用一個數組去收集感興趣的數據。
function collectGroup1(regExp, str) { const matches = []; function replacementFunc(all, first) { matches.push(first); } str.replace(regExp, replacementFunc); return matches; } collectGroup1(/"([^"]*)"/ug,`"foo" and "bar" and "baz"`); // [ 'foo', 'bar', 'baz' ]
對於沒有/g標誌的正則表達式,.replace()僅訪問第一個匹配項。
.test()只要正則表達式匹配成功就會返回true。
const regExp = /a/ug; const str = 'aa'; regExp.test(str); // true regExp.test(str); // true regExp.test(str); // false
你能夠拆分一個字符串並用一個正則表達式去指定分隔符。若是正則表達式包含至少一個捕獲組,那麼.split()將會返回一個數組,其中結果會跟第一個捕獲組互相交替。
const regExp = /<(-+)>/ug; const str = 'a<--->b<->c'; str.split(regExp); // [ 'a', '---', 'b', '-', 'c' ] const regExp = /<(?:-+)>/ug; const str = 'a<--->b<->c'; str.split(regExp); //[ 'a', 'b', 'c' ]
目前這些方法都有如下幾個缺點:
1.它們是冗長且不直觀的。
2.若是標誌/g被設置了,它們纔會工做。有時候,咱們會從其餘地方接收正則表達式,好比經過一個參數。若是咱們想要去肯定全部匹配的項都能找到,那麼不得不檢查/g標誌有沒有被設置。
3.爲了跟蹤進程,全部的方法(除了match)改變了正則表達式的屬性,.lastIndex記錄了上一次匹配的結束爲止。這使得在多個爲止使用相同的正則表達式會存在風險(由於正則表達式的lastIndex屬性改變了,可是你還在別的地方使用這個正則表達式,那麼結果可能會和你想要的不同)。這太惋惜了,當你需屢次調用.exec()的時候,你不能在一個函數內聯一個正則表達式。(由於每次調用,正則表達式都會之重置)。
4.因爲屬性.lastIndex決定在了在哪繼續調用。當咱們開始繼續收集匹配項的時候,就必須把她始終爲0。可是,至少.exec()和其餘一些方法會在最後一次匹配後將.lastIndex重置爲0。若是它不是零,就會發生這種狀況:
const regExp = /a/ug; regExp.lastIndex = 2; regExp.exec('aabb'); // null
這就是你調用.matchAll()的方式:
const matchIterator = str.matchAll(regExp);
給定一個字符串和一個正則表達式,.matchAll()爲全部匹配的匹配對象返回一個迭代器。
你也可使用一個擴展運算符...把迭代器轉換爲數組。
> [...'-a-a-a'.matchAll(/-(a)/ug)] [ [ '-a', 'a' ], [ '-a', 'a' ], [ '-a', 'a' ] ]
如今是否設置/g,都不會有問題了。
> [...'-a-a-a'.matchAll(/-(a)/u)] [ [ '-a', 'a' ], [ '-a', 'a' ], [ '-a', 'a' ] ]
使用.matchAll(),函數collectGroup1() 變得更短更容易理解了。
function collectGroup1(regExp, str) { let results = []; for (const match of str.matchAll(regExp)) { results.push(match[1]); } return results; }
咱們可使用擴展運算符和.map()來使這個函數更簡潔。
function collectGroup1(regExp, str) { let arr = [...str.matchAll(regExp)]; return arr.map(x => x[1]); }
另外一個選擇是使用Array.from(),它會同時轉換數組和映射。所以,你不須要再定義中間值arr。
function collectGroup1(regExp, str) { return Array.from(str.matchAll(regExp), x => x[1]); }
.matchAll()返回一個跌倒器,但不是一個真的可從新利用的迭代器。一旦結果耗盡,你不得再也不次調用方法,獲取一個新的迭代器。
相反,.match()加上 /g 返回一個迭代器即數組,只要你想,你就能夠迭代它。
你如何實現matchAll:
function ensureFlag(flags, flag) { return flags.includes(flag) ? flags : flags + flag; } function* matchAll(str, regex) { const localCopy = new RegExp( regex, ensureFlag(regex.flags, 'g')); let match; while (match = localCopy.exec(str)) { yield match; } }
製做一個本地副本,確保了一下幾件事:
使用matchAll():
const str = '"fee" "fi" "fo" "fum"';
const regex = /"([^"]*)"/;
for (const match of matchAll(str, regex)) { console.log(match[1]); } // Output: // fee // fi // fo // fum
一方面,.matchAll()確實跟批量調用.exec()的工做很像,所以名稱.execAll()會有意義。
另外一方面,exec()改變了正則表達式,而match()沒有。這就解釋了,爲何名字matchAll()會被選擇。