模式和修饰符

  1. 正则表达式(可叫作“regexp”或者“reg”)包含 模式 和可选的 修饰符。创建一个正则表达式对象有两种语法。
  2. 修饰符

字符类

  1. 字符类(Character classes):是一个特殊的符号,匹配特定集中的任何符号。对于每个字符类,都有一个“反向类”,用相同的字母表示,但要以大写书写形式。“反向”表示它与所有其他字符匹配。

Unicode:修饰符 “u” 和 class \p{...}

  1. Unicode 中的每一个字符都具有很多的属性。它们描述了一个字符属于哪个“类别”,包含了各种关于字符的信息。查找具有某种属性的字符,写作 \p{…}。为了顺利使用 \p{…},一个正则表达式必须使用修饰符 u。修饰符 u 在正则表达式中提供对 Unicode 的支持。
  2. 这意味着两件事:
  3. 有了 unicode 属性我们可以查找给定语言中的词,特殊字符(引用,货币)等等。

锚点(Anchors):字符串开始 ^ 和末尾 $

  1. 锚点 ^ 和 $ 属于测试。它们的宽度为零。换句话来说,它们并不匹配一个具体的字符,而是让正则引擎测试所表示的条件(文本开头/文本末尾)。
  2. 这两个锚点 ^...$ 放在一起常常被用于测试一个字符串是否完全匹配一个模式。
  3. ^$:匹配空字符串,开始紧跟着结束。

Flag "m" — 多行模式

  1. 通过 flag /.../m 可以开启多行模式。这仅仅会影响 ^ 和 $ 锚符的行为。在多行模式下,它们不仅仅匹配文本的开始与结束,还匹配每一行的开始与结束。

词边界:\b

  1. 词边界 \b 是一种检查,就像 ^ 和 $ 一样。
  2. 当正则表达式引擎(实现搜索正则表达式的程序模块)遇到 \b 时,它会检查字符串中的位置是否是词边界。
  3. 有三种不同的位置可作为词边界:
  4. \b 既可以用于单词,也可以用于数字。词边界 \b 不适用于非拉丁字母

转义,特殊字符

  1. 反斜杠 "\" 是用来表示匹配字符类的。所以它是一个特殊字符。还存在其它的特殊字符,这些字符在正则表达式中有特殊的含义。它们可以被用来做更加强大的搜索。
  2. 所有特殊字符的列表:[ \ ^ $ . | ? * + ( )
  3. 转义:如果要把特殊字符作为常规字符来使用,只需要在它前面加个反斜杠。这种方式也被叫做“转义一个字符”。
  4. 斜杠符号 '/' 并不是一个特殊符号,但是它被用于在 Javascript 中开启和关闭正则匹配:/...pattern.../,所以我们也应该转义它。
  5. 如果我们使用 new RegExp 来创建一个正则表达式实例,那么我们需要对其做一些额外的转义。
  6. 在字符串中的反斜杠表示转义或者类似 \n 这种只能在字符串中使用的特殊字符。这个引用会“消费”并且解释这些字符,比如说:
  7. 所以调用 new RegExp 会获得一个没有反斜杠的字符串。就像函数会解析并执行一样。如果要修复这个问题,我们需要双斜杠,因为引用会把 \\ 变为 \:
  8. 小结

集合和范围 [...]

  1. 集合:在方括号 […] 中的几个字符或者字符类意味着“搜索给定的字符中的任意一个”。集合可以在正则表达式中和其它常规字符一起使用。请注意尽管在集合中有多个字符,但它们在匹配中只会对应其中的一个
  2. 范围:方括号也可以包含字符范围。比如说,[a-z] 会匹配从 a 到 z 范围内的字母,[0-5] 表示从 0 到 5 的数字。可以在 […] 里面使用字符类。
  3. 字符类是某些字符集的简写
  4. 排除范围:[^…]
  5. 在 […] 中不转义,绝大多数特殊字符可以在不转义的情况下使用,除了在方括号中有特殊含义的字符外,其它所有特殊字符都是允许不添加反斜杠的。
  6. 但是如果你为了“以防万一”转义了它们,这也不会有任何问题。
  7. 但是如果你为了“以防万一”转义了它们,这也不会有任何问题。

量词 `+,*,?` 和 `{n}`

  1. 数量 {n}
  2. 缩写

贪婪量词和惰性量词

  1. 贪婪模式
  2. 默认情况下,正则表达式引擎会尝试尽可能多地重复量词。例如,\d+ 检测所有可能的字符。当不可能检测更多(没有更多的字符或到达字符串末尾)时,然后它再匹配模式的剩余部分。如果没有匹配,则减少重复的次数(回溯),并再次尝试。
  3. 贪婪搜索,为了查找到一个匹配项,正则表达式引擎采用了以下算法:
  4. 对于字符串中的每一个字符
  5. 在贪婪模式下(默认情况下),量词都会尽可能地重复多次。正则表达式引擎尝试用 .+ 去获取尽可能多的字符,然后再一步步地筛选它们。对于这个问题,我们想要另一种结果,这也就是懒惰量词模式的用途。
  6. 懒惰模式
  7. 懒惰模式并不是针对贪婪搜索的灵丹妙药。另一种方式是“微调”贪婪搜索,我们很快就会见到更多的例子。

捕获组

  1. 模式的一部分可以用括号括起来 (...)。这称为“捕获组(capturing group)”。这有两个影响:
  2. 嵌套组:括号可以嵌套。在这种情况下,编号也从左到右。零索引始终保持完全匹配,然后按左括号将组从左到右编号。
  3. 可选组:即使组是可选的并且在匹配项中不存在(例如,具有数量词 (...)?),也存在相应的 result 数组项,并且等于 undefined。
  4. 搜索所有具有组的匹配项:matchAll
  5. matchAll 是一个新方法,可能需要使用 polyfill:https://github.com/ljharb/String.prototype.matchAll
  6. 当我们搜索所有匹配项(标志 g)时,match 方法不会返回组的内容。例如,查找字符串中的所有标签:
  7. 结果是一个匹配数组,但没有每个匹配项的详细信息。但是实际上,我们通常需要在结果中获取捕获组的内容。要获取它们,我们应该使用方法 str.matchAll(regexp) 进行搜索。就像 match 一样,它寻找匹配项,但有 3 个区别:
  8. 由 matchAll 所返回的每个匹配,其格式与不带标志 g 的 match 所返回的格式相同:它是一个具有额外的 index(字符串中的匹配索引)属性和 input(源字符串)的数组:
  9. 为什么 matchAll 的结果是可迭代对象而不是数组?
  10. 命名组:用数字记录组很困难。对于简单模式,它是可行的,但对于更复杂的模式,计算括号很不方便。我们有一个更好的选择:给括号起个名字。这是通过在开始括号之后立即放置 ?<name> 来完成的。
  11. 替换捕获组:
  12. 非捕获组 ?:
  13. 总结
  14. 可以在结果中获得按组匹配的内容:
  15. 如果括号没有名称,则匹配数组按编号提供其内容。命名括号还可使用属性 groups。
  16. 我们还可以使用 str.replace 来替换括号内容中的字符串:使用 $n 或者名称 $<name>。
  17. 可以通过在组的开头添加 ?: 来排除编号组。当我们需要对整个组应用量词,但不希望将其作为结果数组中的单独项时这很有用。我们也不能在替换字符串时引用此类括号。

模式中的反向引用:\N 和 \k<name></name>

  1. 不仅可以在结果或替换字符串中使用捕获组 (...) 的内容,还可以在模式本身中使用它们。
  2. 按编号反向引用:\N,可以使用 \N 在模式中引用一个组,其中 N 是组号。
  3. 按命名反向引用:\k<name>

选择(OR)|

  1. 选择是正则表达式中的一个术语,实际上是一个简单的“或”。在正则表达式中,它用竖线 | 表示。

前瞻断言与后瞻断言

  1. 当我们想根据前面/后面的上下文筛选出一些东西的时候,前瞻断言和后瞻断言(通常被称为“环视断言”)对于简单的正则表达式就很有用。
  2. 有时我们可以手动处理来得到相同的结果,即:匹配所有,然后在循环中按上下文进行筛选。请记住,str.matchAll 和reg.exec 返回的匹配结果有 .index 属性,因此我们能知道它在文本中的确切位置。但通常正则表达式可以做得更好。

灾难性回溯

  1. 有些正则表达式看上去很简单,但是执行起来耗时非常非常非常长,甚至会导致 JavaScript 引擎「挂起」。
  2. 开发者们很容易一不小心就写出这类正则表达式,所以我们迟早会面对这种意外问题。
  3. 如何解决问题?
  4. “灾难性回溯(catastrophic backtracking)”,又译作“回溯陷阱”。我们有 2 种处理它的思路:

粘性标志 "y"

  1. y 标志允许在源字符串中的指定位置执行搜索。
  2. 标记 y 使 regexp.exec 正好在 lastIndex 位置,而不是在它之前,也不是在它之后。

正则表达式(RegExp)和字符串(String)的方法

  1. str.match(regexp):在字符串 str 中找到匹配 regexp 的字符。它有 3 种模式:
  2. str.matchAll(regexp):是 str.match “新改进的”变体。它主要用来搜索所有组的所有匹配项。与 match 相比有 3 个区别:
  3. str.split(regexp|substr, limit):使用正则表达式(或子字符串)作为分隔符来分割字符串。我们可以用 split 来分割字符串,也可以用正则表达式。
  4. str.search(regexp):返回第一个匹配项的位置,如果未找到,则返回 -1;重要限制:search 仅查找第一个匹配项。
  5. str.replace(str|regexp, str|func):这是用于搜索和替换的通用方法,是最有用的方法之一。它是搜索和替换字符串的瑞士军刀。有一个陷阱。当 replace 的第一个参数是字符串时,它仅替换第一个匹配项。要匹配所有,应使用带 g 标记的正则表达式。第二个参数是一个替代字符串。我们可以在其中使用特殊字符:(对于需要“智能”替换的场景,第二个参数可以是一个函数。使用函数可以为我们提供终极替代功能,因为它可以获取匹配项的所有信息,可以访问外部变量,可以做任何事。)
  6. regexp.exec(str):返回字符串 str 中的 regexp 匹配项。与以前的方法不同,它是在正则表达式而不是字符串上调用的。根据正则表达式是否带有标志 g,它的行为有所不同。如果没有 g,那么 regexp.exec(str) 返回的第一个匹配与 str.match(regexp) 完全相同。如果有标记 g,那么:
  7. regexp.test(str):查找匹配项,然后返回 true/false 表示是否存在。