2022 年 10 月 14 日

集合和範圍 [...]

方括號 […] 內的幾個字元或字元類別表示「搜尋給定字元中的任何字元」。

集合

例如,[eao] 表示 3 個字元中的任何一個:'a''e''o'

這稱為集合。集合可以用在正規表示法中,搭配一般字元使用

// find [t or m], and then "op"
alert( "Mop top".match(/[tm]op/gi) ); // "Mop", "top"

請注意,儘管集合中有多個字元,但它們在配對中只對應到一個字元。

因此,以下範例不會產生配對

// find "V", then [o or i], then "la"
alert( "Voila".match(/V[oi]la/) ); // null, no matches

此模式搜尋

  • V,
  • 然後是字母 [oi] 中的一個
  • 然後是 la

因此會找到 VolaVila 的相符項。

範圍

方括號也可能包含字元範圍

例如,[a-z] 是從 az 範圍內的字元,而 [0-5] 是從 05 範圍內的數字。

在以下範例中,我們搜尋的是 "x" 後面接著兩個從 AF 的數字或字母

alert( "Exception 0xAF".match(/x[0-9A-F][0-9A-F]/g) ); // xAF

這裡的 [0-9A-F] 有兩個範圍:它搜尋的字元可能是從 09 範圍內的數字,或從 AF 範圍內的字母。

如果我們也想要搜尋小寫字母,我們可以加入 a-f 範圍:[0-9A-Fa-f]。或加入 i 旗標。

我們也可以在 […] 內使用字元類別。

例如,如果我們想要搜尋一個字元 \w 或連字號 -,那麼這個集合就是 [\w-]

也可以結合多個類別,例如 [\s\d] 表示「空白字元或數字」。

字元類別是特定字元集合的簡寫

例如

  • \d – 與 [0-9] 相同,
  • \w – 與 [a-zA-Z0-9_] 相同,
  • \s – 與 [\t\n\v\f\r ] 相同,外加一些其他較少見的 Unicode 空白字元。

範例:多語言 \w

由於字元類別 \w[a-zA-Z0-9_] 的簡寫,因此它無法找到中文象形文字、西里爾字母等。

我們可以寫一個更通用的模式,用來搜尋任何語言中的字元。這可以使用 Unicode 屬性輕鬆達成:[\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}]

讓我們解讀一下。類似於 \w,我們建立了一個自己的集合,其中包含具有以下 Unicode 屬性的字元

  • Alphabetic (Alpha) – 字母,
  • Mark (M) – 重音,
  • Decimal_Number (Nd) – 數字,
  • Connector_Punctuation (Pc) – 底線 '_' 和類似字元,
  • Join_Control (Join_C) – 兩個特殊代碼 200c200d,用於連字,例如阿拉伯語。

使用範例

let regexp = /[\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}]/gu;

let str = `Hi 你好 12`;

// finds all letters and digits:
alert( str.match(regexp) ); // H,i,你,好,1,2

當然,我們可以編輯這個模式:新增 Unicode 屬性或移除它們。Unicode 屬性在文章 Unicode: flag "u" and class \p{...} 中有更詳細的說明。

Unicode 屬性不支援 IE

Unicode 屬性 p{…} 在 IE 中未實作。如果我們真的需要它們,可以使用函式庫 XRegExp

或僅使用我們感興趣的語言中的字元範圍,例如 [а-я] 表示西里爾字母。

排除範圍

除了正常的範圍之外,還有看起來像 [^…] 的「排除」範圍。

它們在開頭用插入符號 ^ 表示,並與除了給定字元之外的任何字元相符。

例如

  • [^aeyo] – 任何字元,除了 'a''e''y''o'
  • [^0-9] – 任何字元,除了數字,與 \D 相同。
  • [^\s] – 任何非空白字元,與 \S 相同。

以下範例尋找任何字元,除了字母、數字和空白

alert( "[email protected]".match(/[^\d\sA-Z]/gi) ); // @ and .

在 […] 中跳脫

通常當我們想要精確地找到一個特殊字元時,我們需要像 \. 一樣跳脫它。如果我們需要反斜線,則使用 \\,依此類推。

在方括號中,我們可以在不跳脫的情況下使用絕大多數特殊字元

  • 符號 . + ( ) 永遠不需要跳脫。
  • 連字元 - 在開頭或結尾(它不定義範圍的地方)不會跳脫。
  • 插入符號 ^ 僅在開頭(表示排除)時跳脫。
  • 閉合方括號 ] 總是跳脫(如果我們需要尋找該符號)。

換句話說,除了方括號的意義之外,所有特殊字元都可以在不跳脫的情況下使用。

方括號內的句點 . 僅表示句點。模式 [.,] 會尋找其中一個字元:句點或逗號。

在以下範例中,正規表示式 [-().^+] 尋找其中一個字元 -().^+

// No need to escape
let regexp = /[-().^+]/g;

alert( "1 + 2 - 3".match(regexp) ); // Matches +, -

…但如果你決定「以防萬一」跳脫它們,那不會有任何害處

// Escaped everything
let regexp = /[\-\(\)\.\^\+]/g;

alert( "1 + 2 - 3".match(regexp) ); // also works: +, -

範圍和旗標「u」

如果集合中有代理對,則需要旗標 u 才能正確運作。

例如,讓我們在字串 𝒳 中尋找 [𝒳𝒴]

alert( '𝒳'.match(/[𝒳𝒴]/) ); // shows a strange character, like [?]
// (the search was performed incorrectly, half-character returned)

結果不正確,因為預設正規表示式「不知道」代理對。

正規表示式引擎認為 [𝒳𝒴] – 不是兩個,而是四個字元

  1. 𝒳 的左半邊 (1)
  2. 𝒳 的右半邊 (2)
  3. 𝒴 (3) 的左半邊
  4. 𝒴 (4) 的右半邊

我們可以看到它們的程式碼如下

for(let i=0; i<'𝒳𝒴'.length; i++) {
  alert('𝒳𝒴'.charCodeAt(i)); // 55349, 56499, 55349, 56500
};

因此,上面的範例會找到並顯示 𝒳 的左半邊

如果我們新增旗標 u,則行為會正確

alert( '𝒳'.match(/[𝒳𝒴]/u) ); // 𝒳

在尋找範圍(例如 [𝒳-𝒴])時,會發生類似的狀況

如果我們忘記新增旗標 u,就會發生錯誤

'𝒳'.match(/[𝒳-𝒴]/); // Error: Invalid regular expression

原因是沒有旗標 u,代理對會被視為兩個字元,因此 [𝒳-𝒴] 會被解釋為 [<55349><56499>-<55349><56500>](每個代理對都會被其程式碼取代)。現在很容易看出範圍 56499-55349 無效:它的起始程式碼 56499 大於結束程式碼 55349。這是發生錯誤的正式原因

使用旗標 u,模式會正確運作

// look for characters from 𝒳 to 𝒵
alert( '𝒴'.match(/[𝒳-𝒵]/u) ); // 𝒴

任務

我們有一個正規表示式 /Java[^script]/

它是否會在字串 Java 中找到任何內容?在字串 JavaScript 中呢?

答案:否,是

  • 在腳本 Java 中,它不會找到任何內容,因為 [^script] 表示「任何字元,除了給定的字元」。因此,正規表示式會尋找後面接著一個此類符號的 "Java",但有一個字串結尾,其後沒有符號

    alert( "Java".match(/Java[^script]/) ); // null
  • 是的,因為 [^script] 部分會比對字元 "S"。它不是 script 中的一個。由於正規表示式區分大小寫(沒有 i 旗標),它會將 "S" 視為與 "s" 不同的字元

    alert( "JavaScript".match(/Java[^script]/) ); // "JavaS"

時間可以採用 小時:分鐘小時-分鐘 格式。小時和分鐘都有 2 個數字:09:0021-30

撰寫一個正規表示式來尋找時間

let regexp = /your regexp/g;
alert( "Breakfast at 09:00. Dinner at 21-30".match(regexp) ); // 09:00, 21-30

附註:在這個任務中,我們假設時間總是正確的,不需要過濾掉像「45:67」這樣的錯誤字串。稍後我們也會處理這個問題

答案:\d\d[-:]\d\d

let regexp = /\d\d[-:]\d\d/g;
alert( "Breakfast at 09:00. Dinner at 21-30".match(regexp) ); // 09:00, 21-30

請注意,連字符 '-' 在方括號中具有特殊含義,但僅在其他字元之間,而不是在開頭或結尾時,因此我們不需要跳脫它

教學課程地圖

留言

留言前請先閱讀…
  • 如果您有建議要如何改進 - 請 提交 GitHub 問題 或提交拉取請求,而不是留言。
  • 如果您無法理解文章中的某些內容 - 請詳細說明。
  • 若要插入幾行程式碼,請使用 <code> 標籤,對於多行程式碼 - 請使用 <pre> 標籤,對於超過 10 行的程式碼 - 請使用沙盒 (plnkrjsbincodepen…)