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 属性的字符

  • 字母 (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{...} 中有更详细的介绍。

IE 不支持 Unicode 属性

IE 中未实现 Unicode 属性 p{…}。如果我们确实需要它们,可以使用库 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"

时间可以是 hours:minuteshours-minutes 格式。小时和分钟都有两位数字: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…)