進階的正則表達式

「若是你有一個問題想到能夠用正則來解決,那麼你如今有兩個問題了。」 🤷‍♀️html

青銅-正則基礎

正則表達式是用於匹配字符串中字符組合的模式。git

建立正則表達式

  • 使用正則表達式字面量建立 /ab+c/g
  • 調用 RegExp 對象的構造函數建立 new RegExp("ab+c","g")
    • 接收兩個參數,第一個參數是字符串或正則表達式,第二個參數是修飾符(flag)
    • 若是第一個參數是正則表達式,那麼只使用會使用第二個參數的修飾符,而忽略原有正則表達式的修飾符(ES6 擴展)

RegExp 對象

1. 實例屬性和方法

  • RegExp.prototype.exec(str)
  • RegExp.prototype.test(str)
  • RegExp.prototype.flags (ES6) 返回正則表達式的修飾符
  • RegExp.prototype.sticky (ES6) 表示是否設置了 y 修飾符 ...

2. 靜態屬性

  • RegExp.lastIndex

3. 字符串對象

有 6 個方法可使用正則表達式es6

  • str.search(regexp)正則表達式

  • str.match(regexp) 返回一個數組數組

  • str.replace(regexp|substr, newSubStr|function)markdown

  • str.split(separator) 分隔符 separator 包括 str|regexpapp

  • str.matchAll() - ES2020 新增ide

  • str.replaceAll() - ES2020 新增函數

4. RegExp.prototype.test(str) 和 String.prototype.search(regexp)

  • test() 判斷正則表達式與指定的字符串是否匹配,返回 true 或 false。
  • 相似於 String 的 search() 方法,返回匹配的索引,不然返回-1
let str = "hello world!";
/world/.test(str); // true

let str = "hello world!";
str.search(/world/); // 6
複製代碼

若想知道更多返回信息(然而執行比較慢),可以使用 exec() 方法 ⬇️ ⬇️ ⬇️oop

5. RegExp.prototype.exec(str) 和 String.prototype.match(regexp)

  • exec() 在指定字符串中搜索匹配。匹配成功返回一個數組,並更新正則表達式對象的 lastIndex 屬性
    • 數組包括:第一項是匹配成功的文本、第二項起是相關的捕獲組內容、以及其餘屬性(index 匹配到的索引值 、input 原始字符串、groups 命名捕獲組)
  • match() 也是返回一個數組,包括第一個完整匹配,及其相關的捕獲組(返回結果與 exec() 方法相同)
  • 當 match() 方法使用 g 標誌,會返回匹配的全部結果
// 返回結果相同
let str = "hello world world!";
/world/.exec(str);

let str = "hello world world!";
str.match(/world/);

// 會返回匹配的全部結果
let str = "hello world world!";
str.match(/world/g);
複製代碼

編寫一個正則表達式

正則表達式由簡單字符 + 特殊字符組成

1. 6 個可選標識 (flags)

正則表達式有六個可選參數 (flags) 容許全局和不分大小寫搜索等

  • g 全局搜索 global
  • i 不區分大小寫搜索 ignorecase
  • m 多行搜索 multiline
  • s 容許 . 匹配換行符 (ES2018)
  • u unicode 模式匹配 (ES6)
  • y 執行「粘性(sticky)」搜索,匹配從目標字符串的當前位置開始 (ES6)

語法:

  • let regExp = /pattern/flags;
  • let regExp = new RegExp("pattern", "flags");
let str = "Hello World!";
/world/i.test(str); // true
複製代碼

2. 特殊字符

在正則表達式中具備特殊意義的專用字符,能夠分爲: 特殊字符、量詞、範圍/組、斷言、Unicode 屬性轉義。

1. 特殊單字符

  • . 匹配任意字符(除換行符外)
  • \d 匹配數字 digit => [0-9]
  • \D 匹配非數字 => [^0-9]
  • \w 匹配一個字符 word(包括字母數字下劃線) => [A-Za-z0-9_]
  • \W 匹配非字符 => [^a-za-z0-9_]
  • \s 匹配空白字符 space,包括空格、製表符、換頁符和換行符
  • \S 匹配非空白字符
  • \b 匹配一個單詞的邊界 boundary
  • \r 匹配回車符
  • \n 匹配換行符
  • \uhhhh 匹配十六進制數表示的 Unicode 字符
  • \u{hhhh} 匹配十六進制數表示的 Unicode 字符(ES6 新增寫法,須要設置 u 標誌)
let str = "He played the King in a8 and she moved her Queen in c2.";
str.match(/\w\d/g); // ["a8","c2"]
複製代碼
// 匹配 Unicode 字符
let str = "happy 🙂, confused 😕, sad 😢";
let reg = /[\u{1F600}-\u{1F64F}]/gu;
str.match(reg); // ['🙂', '😕', '😢']
複製代碼
// 匹配中文字符 [\u4e00-\u9fa5]
let str = "123我是456中文";
let reg = /[\u4e00-\u9fa5]/g;
str.match(reg); // ["我", "是", "中", "文"]
複製代碼

2. 量詞

  • * 匹配 0 次以上(0+ 即有沒有都行) => {0,}

  • + 匹配 1 次以上 (1+ 即至少一次)=> {1,}

  • ? 匹配 0 或 1 次(可選,可能有可能沒有,有點像 TS 的可選)=> {0,1}

  • {n} 匹配字符恰好出現 n 次

  • {n,m} 至少 n 次,最多 m 次

  • {n,} 至少出現了 n 次

// 匹配規則:一個或多個字符 和 一個空格,全局匹配,忽略大小寫
let re = /\w+\s/gi;
"fee fi fo fum".match(re); // ["fee ", "fi ", "fo "]
複製代碼

3. 範圍 Range / 組 group

  • [xyz] 字符集合,匹配方括號中的任意字符, 破折號(-)能夠指定範圍
  • [^xyz] 反向字符集,匹配任何沒有包含在方括號中的字符
  • x|y 匹配 x 或 y
let str = "The Caterpillar and Alice looked at each other";
let reg = /\b[a-df-z]+\b/gi;
str.match(reg);  // ["and", "at"]
複製代碼
  • (x) 1. 分組 2. 捕獲,匹配 x 並記住匹配項,後續經過 \n 來引用第 n 個捕獲的組,替換時使用 $n 來指代。
  • (?:x) 非捕獲括號,匹配的子字符串不會被記住,能夠節省性能
let reg = /(apple) (banana) \1 \2/;
"apple banana apple banana apple banana".match(reg);
複製代碼
let reg = /(\w+)\s(\w+)/;
let str = "John Smith";
str.replace(reg, "$2 $1"); // "Smith, John"
複製代碼

4. 斷言-主要是對邊界的判斷

  • ^ 匹配輸入的開始(注意:字符集合[^xyz]中表示反向)

  • $ 匹配輸入的結束

  • \b 匹配一個單詞的邊界

  • x(?=y) 先行斷言,匹配 x (僅當後面爲 y) 如: /Jack(?=Sparrow)/ 匹配 Jack

  • (?<=y)x 後行斷言(ES2018),匹配 x (僅當前面爲 y) /(?<=Jack)Sparrow/ 匹配 Sparrow

let str = "https://xxx.xx.com/#/index?type=xx&value=xxx";
let reg = /(?<=\?).+/g;
str.match(reg); // ['type=xx&value=xxx']

// 條件過濾
let oranges = ["ripe orange A", "green orange B", "ripe orange C"];
oranges.filter((item) => item.match(/(?<=ripe )orange/)); //  ["ripe orange A", "ripe orange C"]
複製代碼
  • x(?!y) 先行否認斷言,匹配 x (僅當後面不爲 y) /Jack(?!Sparrow)/
  • (?<!y)x 後行否認斷言(ES2018),匹配 x (僅當前面不爲 y) /(?<!Jack)Sparrow/

白銀-正則進階

下面主要是一些(ES6 新增)修飾符與對應的屬性

g 修飾符 與 lastIndex 屬性

  • lastIndex 用來指定「下一次匹配的起始索引」,須要設置 g 標誌才生效
  • 由於在設置了 g 標誌位的狀況下,RegExp 對象是有狀態的,會將上次成功匹配後的位置記錄在 lastIndex 屬性中
  • 使用 exec() / test() 方法匹配成功後,會更新正則對象的 lastIndex 屬性,匹配失敗 lastIndex 重置爲 0
let regExp = /ab*/g;
regExp.exec("abbcdefabh"); // ['abb',index:0]
regExp.lastIndex; // 3
// 繼續匹配
regExp.exec("abbcdefabh"); // ['ab',index:7]
regExp.lastIndex; // 9
// 再繼續匹配
regExp.exec("abbcdefabh"); // null
regExp.lastIndex; // 0
複製代碼

有了上述特性,exec() / test () 方法可對字符串進行循環匹配(查找出全部匹配)

let reg = /ab*/g;
let str = "abbcdefabh";
let arr = [];
while ((arr = reg.exec(str)) !== null) {
  console.log(arr, reg.lastIndex);
}
// 對比 match ,只會返回匹配到的結果
str.match(reg); // ['abb','ab']
複製代碼

y 修飾符 與 sticky 屬性(ES6)

  • y 也叫作「粘連」修飾符,也是全局匹配
  • 與 g 修飾符區別是,g 修飾符只要剩餘位置中存在匹配就可,而 y 修飾符確保「匹配必須從剩餘的第一個位置開始」,即粘連。
let regExp = /ab*/y;
regExp.exec("abbcdefabh"); // ['abb',index:0]
regExp.lastIndex; // 3
// 繼續匹配
regExp.exec("abbcdefabh"); //null
regExp.lastIndex; // 0

regExp.sticky; // true 表示設置了y修飾符
複製代碼

理解: y 修飾符號隱含了頭部匹配的標誌。y 修飾符的設計本意,就是讓頭部匹配的標誌^在全局匹配中都有效。

u 修飾符 與 unicode 屬性(ES6)

  • u 修飾符用來匹配大於 \uFFFF 的 Unicode 字符 (ES6)(\uhhhh 匹配十六進制數表示的 Unicode 字符)
  • unicode 屬性,表示是否設置了 u 修飾符
/^\uD83D/.test('\uD83D\uDC2A') // true "\uD83D\uDC2A"表明一個字符
/^\uD83D/u.test('\uD83D\uDC2A') // false

let  r = /hello/u;
r.unicode; // true
複製代碼

s 修飾符 與 dotAll 屬性(ES6)

  • ES5 中 . 匹配任意字符(除換行符外)
  • ES2018 新增 s 修飾符,使得 . 能夠匹配任意單個字符,稱爲 dotAll 模式。
/foo.bar/.test("foo\nbar"); // false
// ES2018
/foo.bar/s.test("foo\nbar"); // true
/foo.bar/s.dotAll; // true
複製代碼

黃金-正則深刻

具名組匹配

1. 組匹配

// exec() 返回數組的第一項是匹配成功的文本,從第二項起,每項都對應「捕獲括號」裏匹配成功的文本
let regex = /(\d{4})-(\d{2})-(\d{2})/;
regex.exec("1999-12-31"); // ["1999-12-31", "1999", "12", "31", index: 0,groups: undefined]
複製代碼

每一組的匹配含義不容易看出來,並且只能用數字序號引用 \n

2. 具名組匹配 (ES2018)

容許爲每個組匹配指定一個名字,既便於閱讀代碼,又便於引用。

語法: /?<組名字>(x)/

let regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
regex.exec("1999-12-31");
// ["1999-12-31", "1999", "12", "31", index: 0,groups: {day: "31",month: "12",year: "1999"}]
複製代碼

3. 解構賦值

將匹配結果返回的數組直接解構

let {
  groups: { one, two },
} = /^(?<one>.*):(?<two>.*)$/u.exec("foo:bar");
one; // foo
two; // bar
複製代碼

4. 替換

替換時,用 %<組名字> 引用具名組

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;

"2015-01-02".replace(re, "$<day>/$<month>/$<year>");
// '02/01/2015'
複製代碼

字符串(新增)方法

String.prototype.matchAll(regexp) (ES2020)

  • matchAll() 方法能夠一次性取出全部匹配,且包含捕獲組。返回的是一個遍歷器(Iterator)
  • 正則表達式必須設置全局模式 g ,不然會拋出異常 TypeError

在 matchAll 出現以前,經過在循環中調用 regexp.exec() 來獲取全部匹配項信息 若是使用 matchAll ,就能夠沒必要使用 while 循環加 exec 方式了

let regexp = /t(e)(st(\d?))/g;
let str = "test1test2";

// match 方式匹配
str.match(regexp); // ['test1', 'test2']

// exec 方式匹配
regexp.exec(str); //  ["test1", "e", "st1", "1", index: 0 ]

// matchAll 方式匹配,能夠更好地獲取捕獲組
[...str.matchAll(regexp)]; // [Array(4), Array(4)]
複製代碼

String.prototype.replace(regexp|substr, newSubStr|function)

當第一個參數爲正則表達式,第二個參數爲函數時:

  • str.replace(regexp, function)
  • function 參數以下,也是返回一個新字符串,來替換 regexp 匹配到的結果
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
"2015-01-02".replace(
  re,
  ( matched, // 匹配結果 capture1, // 匹配組1(必須對應上) capture2, // 匹配組2 capture3, // 匹配組3 index, // index input, // input groups // 具名組 ) => {
    console.log(matched, capture1, capture2, capture3, index, input, groups);
    let { day, month, year } = groups;
    return `${day}/${month}/${year}`;
  }
); // "02/01/2015"
複製代碼

String.prototype.replaceAll(regexp|substr, newSubstr|function) (ES2021)

  • 能夠一次性替換全部匹配
  • 當第一個參數爲正則表達式(必須帶 g 修飾符),第二個參數爲函數時 function 參數 與 replace 用法相同

參考

正則表達式

regexper

c.runoob.com/front-end/8…

阮一峯-正則的擴展

Unicode 編碼在線轉換

Unicode 與 JavaScript 詳解