1. 引言
本週精讀的文章是 regexp-features-regular-expressions。
這篇文章介紹了 ES2018 正則支援的幾個重要特性:
- Lookbehind assertions - 後行斷言
- Named capture groups - 命名捕獲組
- s (dotAll) Flag - . 匹配任意字元
- Unicode property escapes - Unicode 屬性轉義
2. 概述
還在用下標匹配內容嗎?匹配任意字元只有 [\w\W]
嗎?現在正則有更簡化的寫法了,事實上正則正在變得更加易用,是時候更新對正則的認知了。
2.1. Lookbehind assertions
完整的斷言定義分為:正/負向斷言 與 先/後行斷言 的笛卡爾積組合,在 ES2018 之前僅支援先行斷言,現在終於支援了後行斷言。
解釋一下這四種斷言:
正向先行斷言 (?=...)
表示之後的字串能匹配 pattern。
const re = /Item(?= 10)/;
console.log(re.exec("Item"));
// → null
console.log(re.exec("Item5"));
// → null
console.log(re.exec("Item 5"));
// → null
console.log(re.exec("Item 10"));
// → ["Item", index: 0, input: "Item 10", groups: undefined]
複製程式碼
負向先行斷言 (?!...)
表示之後的字串不能匹配 pattern。
const re = /Red(?!head)/;
console.log(re.exec("Redhead"));
// → null
console.log(re.exec("Redberry"));
// → ["Red", index: 0, input: "Redberry", groups: undefined]
console.log(re.exec("Redjay"));
// → ["Red", index: 0, input: "Redjay", groups: undefined]
console.log(re.exec("Red"));
// → ["Red", index: 0, input: "Red", groups: undefined]
複製程式碼
在 ES2018 後,又支援了兩種新的斷言方式:
正向後行斷言 (?<=...)
表示之前的字串能匹配 pattern。
先行時字串放前面,pattern 放後面;後行時字串放後端,pattern 放前面。先行匹配以什麼結尾,後行匹配以什麼開頭。
const re = /(?<=€)\d+(\.\d*)?/;
console.log(re.exec("199"));
// → null
console.log(re.exec("$199"));
// → null
console.log(re.exec("€199"));
// → ["199", undefined, index: 1, input: "€199", groups: undefined]
複製程式碼
負向後行斷言 (?<!...)
表示之前的字串不能匹配 pattern。
注:下面的例子表示 meters 之前 不能匹配 三個數字。
const re = /(?<!\d{3}) meters/;
console.log(re.exec("10 meters"));
// → [" meters", index: 2, input: "10 meters", groups: undefined]
console.log(re.exec("100 meters"));
// → null
複製程式碼
文中給了一個稍複雜的例子,結合了 正向後行斷言 與 負向後行斷言:
注:下面的例子表示 meters 之前 能匹配 兩個數字,且 之前 不能匹配 數字 35.
const re = /(?<=\d{2})(?<!35) meters/;
console.log(re.exec("35 meters"));
// → null
console.log(re.exec("meters"));
// → null
console.log(re.exec("4 meters"));
// → null
console.log(re.exec("14 meters"));
// → ["meters", index: 2, input: "14 meters", groups: undefined]
複製程式碼
2.2. Named Capture Groups
命名捕獲組可以給正則捕獲的內容命名,比起下標來說更可讀。
其語法是 ?<name>
:
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const [match, year, month, day] = re.exec("2020-03-04");
console.log(match); // → 2020-03-04
console.log(year); // → 2020
console.log(month); // → 03
console.log(day); // → 04
複製程式碼
也可以在正規表示式中,通過下標 \1
直接使用之前的捕獲組,比如:
解釋一下,
\1
代表(\w\w)
匹配的內容而非(\w\w)
本身,所以當(\w\w)
匹配了'ab'
後,\1
表示的就是對'ab'
的匹配了。
console.log(/(\w\w)\1/.test("abab")); // → true
// if the last two letters are not the same
// as the first two, the match will fail
console.log(/(\w\w)\1/.test("abcd")); // → false
複製程式碼
對於命名捕獲組,可以通過 \k<name>
的語法訪問,而不需要通過 \1
這種下標:
下標和命名可以同時使用。
const re = /\b(?<dup>\w+)\s+\k<dup>\b/;
const match = re.exec("I'm not lazy, I'm on on energy saving mode");
console.log(match.index); // → 18
console.log(match[0]); // → on on
複製程式碼
2.3. s (dotAll) Flag
雖然正則中 .
可以匹配任何字元,但卻無法匹配換行符。因此聰明的開發者們用 [\w\W]
巧妙的解決了這個問題。
然而這終究是個設計缺陷,在 ES2018 支援了 /s
模式,這個模式下,.
等價於 [\w\W]
:
console.log(/./s.test("\n")); // → true
console.log(/./s.test("\r")); // → true
複製程式碼
2.4. Unicode Property Escapes
正則支援了更強大的 Unicode 匹配方式。在 /u
模式下,可以用 \p{Number}
匹配所有數字:
u 修飾符可以識別所有大於 0xFFFF 的 Unicode 字元。
const regex = /^\p{Number}+$/u;
regex.test("²³¹¼½¾"); // true
regex.test("㉛㉜㉝"); // true
regex.test("ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ"); // true
複製程式碼
\p{Alphabetic}
可以匹配所有 Alphabetic 元素,包括漢字、字母等:
const str = "漢";
console.log(/\p{Alphabetic}/u.test(str)); // → true
// the \w shorthand cannot match 漢
console.log(/\w/u.test(str)); // → false
複製程式碼
終於有簡便的方式匹配漢字了。
2.5. 相容表
可以到 原文 檢視相容表,總體上只有 Chrome 與 Safari 支援,Firefox 與 Edge 都不支援。所以大型專案使用要再等幾年。
3. 精讀
文中列舉的四個新特性是 ES2018 加入到正則中的。但正如相容表所示,這些特性基本還都不能用,所以不如我們再溫習一下 ES6 對正則的改進,找一找與 ES2018 正則變化的結合點。
3.1. RegExp 建構函式優化
當 RegExp 建構函式第一個引數是正規表示式時,允許指定第二個引數 - 修飾符(ES5 會報錯):
new RegExp(/book(?=s)/giu, "iu");
複製程式碼
不痛不癢的優化,,畢竟大部分時間建構函式不會這麼用。
3.2. 字串的正則方法
將字串的 match()
、replace()
、search
、split
方法內部呼叫時都指向到 RegExp 的例項方法上,比如
String.prototype.match
指向 RegExp.prototype[Symbol.match]
。
也就是正規表示式原本應該由正則例項觸發,但現在卻支援字串直接呼叫(方便)。但執行時其實指向了正則例項物件,讓邏輯更為統一。
舉個例子:
"abc".match(/abc/g) /
// 內部執行時,等價於
abc /
g[Symbol.match]("abc");
複製程式碼
3.3. u 修飾符
概述中,Unicode Property Escapes 就是對 u 修飾符的增強,而 u
修飾符是在 ES6 中新增的。
u
修飾符的含義為 “Unicode 模式”,用來正確處理大於 \uFFFF
的 Unicode 字元。
同時 u
修飾符還會改變以下正規表示式的行為:
- 點字元原本支援單字元,但在
u
模式下,可以匹配大於0xFFFF
的 Unicode 字元。 - 將
\u{61}
含義由匹配 61 個u
改編為匹配 Unicode 編碼為 61 號的字母a
。 - 可以正確識別非單字元 Unicode 字元的量詞匹配。
\S
可以正確識別 Unicode 字元。u
模式下,[a-z]
還能識別 Unicode 編碼不同,但是字型很近的字母,比如\u212A
表示的另一個K
。
基本上,在 u
修飾符模式下,所有 Unicode 字元都可以被正確解讀,而在 ES2018,又新增了一些 u
模式的匹配集合來匹配一些常見的字元,比如 \p{Number}
來匹配 ¼
。
3.4. y 修飾符
y
修飾符是 “粘連”(sticky)修飾符。
y
類似 g
修飾符,都是全域性匹配,也就是從上次成功匹配位置開始,繼續匹配。y
的區別是,必須是上一次匹配成功後的下一個位置就立即匹配才算成功。
比如:
/a+/g.exec("aaa_aa_a"); // ["aaa"]
複製程式碼
3.5. flags
通過 flags
屬性拿到修飾符:
const regex = /[a-z]*/gu;
regex.flags; // 'gu'
複製程式碼
4. 總結
本週精讀藉著 regexp-features-regular-expressions 這篇文章,一起理解了 ES2018 新增的正則新特性,又順藤摸瓜的整理了 ES6 對正則做的增強。
如果你擅長這種擴散式學習方式,不妨再進一步溫習一下整個 ES6 引入的新特性,筆者強烈推薦阮一峰老師的 ECMAScript 6 入門 一書。
ES2018 引入的特性還太新,單在對 ES6 特性的使用應該和對 ES3 一樣熟練。
如果你身邊的小夥伴還對 ES6 特性感到驚訝,請把這篇文章分享給他,防止退化為 “只剩專案經驗的 JS 入門者”。
如果你想參與討論,請點選這裡,每週都有新的主題,週末或週一釋出。前端精讀 - 幫你篩選靠譜的內容。