【前端】JavaScript基础(正则表达式)

9/2/2022 JS

# 概述

# 模式和修饰符

  • 创建 RegExp 对象的两种语法
    • regexp = new RegExp("pattern");:可动态创建表达式
    • regexp = /pattern/;:不允许插入表达式
  • 修饰符(6 个)
    • i:搜索时不区分大小写
    • g:搜索时会寻找所有的匹配项,否则仅返回第一个匹配项
    • m:多行模式
    • s:启用 dotall 模式,允许点 . 匹配换行符 \n
    • u:开启完整的 Unicode 支持,能够正确处理代理对
    • y:粘滞模式,在文本中的确切位置搜索
  • 搜索str.match(regexp)
    • 若正则表达式包含修饰符 g,返回一个由所有匹配项所构成的数组
    • 若没有修饰符,则会以数组形式返回第一个匹配项
    • 如果没有匹配项,则返回 null
    • 注:若希望结果始终是一个数组,则 matches = str.match(regexp) || []
  • 替换str.replace(regexp, replacement)
    • 若带有修饰符 g,则替换所有匹配项,否则只替换第一个
  • 测试regexp.test(str)
    • 至少找到一个匹配项,如果找到了,则返回 true,否则返回 false

# 字符类

  • 数字类\d,对应于任何一位数字,即从 09 的字符
  • 空格符\s,包括空格、制表符 \t,换行符 \n 和其他少数稀有字符,例如 \v\f\r
  • 单字字符\w,拉丁字母或数字或下划线
  • 非数字\D,除 \d 以外的任何字符
  • 非空格\S,除 \s 以外的任何字符
  • 非单字\W,除 \w 以外的任何字符,例如非拉丁字母或空格
  • .:与换行符 \n 之外的任何字符匹配(带有修饰符 \s 时,. 匹配任何字符)
  • 任何字符[\s\S][\d\D][^] ...

# 字符串开始和末尾标识符

  • ^:匹配文本开头
  • $:匹配文本末尾
  • ^...$:用于测试一个字符串是否完全匹配一个模式
  • 在多行(\m)模式下,匹配每一行的开始与结尾
  • 注:锚点 ^$ 属于测试,宽度为零,区别于换行符 \n

# 词边界

  • 有三种不同的位置可作为词边界(\b
    • 在字符串开头,如果第一个字符是单词字符 \w
    • 在字符串中的两个字符之间,其中一个是单词字符 \w,另一个不是
    • 在字符串末尾,如果最后一个字符是单词字符 \w
  • \b 既可以用于单词,也可以用于数字(如,\b\d\d\b 查找独立的两位数)
  • 词边界测试 \b 检查该位置的一侧是否匹配 \w,而另一侧则不匹配 \w

# 特殊字符和转义

  • 特殊字符:[ ] { } ( ) \ ^ $ . | ? * +,使用反斜杠进行转义
  • 如果在 /.../ 内(但不在 new RegExp 内),需要转义斜杠
  • 如果没使用 /.../,而是使用另一种 new RegExp 的方式创建正则表达式,则不需要转义斜杠
  • 当将字符串传递给给 new RegExp 时,我们需要双反斜杠 \\,因为字符串引号会消耗一个反斜杠

# 集合和范围

  • [] 中的几个字符或者字符类表示:搜索给定字符中的任意一个
  • [abc]:表示集合,字母 a/b/c 中的任意一个
  • [0-9a-z]:表示范围,从 0 到 9 或 a 到 z 范围内的任意一个字符([\s\d] 表示一个空格或一个数字)
  • [^0-9]:表示排除范围,匹配除了数字之外的任何字符,与 \D 作用相同

# 量词

  • {n}:表示数量。例如,\d{5} 表示 5 位数;\d{3,5} 表示 3-5 位的数字;\d{3,} 表示查找位数大于等于 3 的数字
  • +:代表一个或多个,与 {1,} 相同。例如,\d+ 表示所有数字
  • ?:代表零个或一个,与 {0,1} 相同,即字符是可选的
  • *:代表零个及以上,与 {0,} 相同,即字符可以出现任何次数或者不出现

# 贪婪量词和惰性量词

  • 贪婪模式:默认情况下,量词会尽可能多地向后匹配,然后当模式的剩余部分不匹配时,再回溯直至匹配剩余的部分
  • 惰性模式:通过在量词后面添加一个 ? 来启用,如 *?+??? 等。量词尽可能少地向后匹配
  • 惰性模式的另一种等同的做法是:使用排除项微调贪婪搜索,如 /"[^"]+"/g

# 捕获组

  • 模式的一部分可以用括号括起来,称为捕获组
    • 它允许将匹配的一部分作为结果数组中的单独项
    • 如果将量词放在括号后,则它将括号视为一个整体
    • 包括:嵌套组、可选组以及命名组
  • /(go)+/i:可以匹配 gogogoGogo 等(括号被从左到右编号)
  • str.match(regexp):如果 regexp 没有修饰符 g,将查找第一个匹配项,并将它作为数组返回
    • 在索引 0 处:完整的匹配项
    • 在索引 1 处:第一个括号的内容
    • 在索引 2 处:第二个括号的内容
    • 以此类推
  • 嵌套组:嵌套的括号,编号同样从左到右
    • 例如,<(([a-z]+)\s*([^>]*))>
    • 索引 0 中始终保存的是正则表达式的完整匹配项
  • 可选组:即使组是可选的且在匹配项中不存在,也存在相应的 result 数组项,并且等于 undefined
    • 例如,a(z)?(c)?
    • 索引 0 中始终保存的是正则表达式的完整匹配项
  • 当搜索所有匹配项(带修饰符 g)时,match 方法不会返回组的内容
    • 例如,str.match(/<(.*?)>/g) 结果是一个匹配数组,但没有每个匹配项的详细信息
    • 使用 str.matchAll(regexp) 进行搜索
      • 它返回的不是数组,而是一个可迭代的对象(使用 Array.from 将其转换为数组)
      • 当存在修饰符 g 时,它将每个匹配项以包含组的数组的形式返回
      • 如果没有匹配项,则返回的不是 null,而是一个空的可迭代对象
  • 命名组:在左括号后紧跟着放置 ?<name> 即可完成对括号的命名
    • 例如,(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})
    • 匹配的组在 groups 属性中:str.match(dateRegexp).groups.year
  • str.replace(regexp, replacement) 中的 replacement 可以使用 $n 来交换组的内容,其中 n 是组号
    • 例如,"John Bull".replace(/(\w+) (\w+)/, '$2, $1'),变为 "Bull, John"
    • 对于命名的括号,引用为 $<name>
  • 非捕获组:通过在开头添加 ?: 来排除组
    • 例如,"Gogogo John!".match(/(?:go)+ (\w+)/i),结果数组中便删除了第一个组
    • 索引 0 中始终保存的是正则表达式的完整匹配项

# 模式中的反向引用

  • 按编号反向引用:\N,其中 N 是组号
    • 例如,/(['"])(.*?)\1/g,找到第一个引号 (['"]) 并记住其内容
    • 可以确保模式查找的结束引号与开始的引号完全相同
    • 若在捕获组中使用 ?:,将无法引用它。对于使用 (?:...) 捕获的组被排除,引擎不会记住它
  • 按命名反向引用:\k<name>
    • 例如,/(?<quote>['"])(.*?)\k<quote>/g

# 选择

  • |:允许在多个字符中进行选择
    • 例如,gr[ae]y 等同于 gr(a|e)y
    • 例如,gra|ey 表示 graey
    • 要将选择应用于模式中一部分内容的选择,可以将其括在括号中:love (HTML|CSS) 匹配 love HTMLlove CSS

# 前瞻断言与后瞻断言

  • 前瞻断言X(?=Y),表示仅在后面是 Y 时匹配 X
    • 当查找 X(?=Y) 时,正则表达式引擎会找到 X,然后检查其后是否有 Y。如果没有,则跳过潜在匹配,并继续搜索。
    • X(?=Y)(?=Z) 匹配 X 后跟 YZ(模式 YZ 不互斥)
    • 例如,"1 turkey costs 30€".match(/\d+(?=€)/) 结果是 30
    • 注:前瞻断言只是一个测试,括号 (?=...) 中的内容不包含在匹配结果中
    • 否定的前瞻断言X(?!Y),匹配 X,但前提是后面没有 Y
  • 后瞻断言:匹配前面有特定字符串的模式
    • 注:非 V8 引擎的浏览器不支持后瞻断言
    • 肯定的后瞻断言:(?<=Y)X,匹配 X,仅在前面是 Y 的情况下
      • 例如,"1 turkey costs $30".match(/(?<=\$)\d+/),结果是 30
    • 否定的后瞻断言:(?<!Y)X,匹配 X,仅在前面不是 Y 的情况下
      • 例如,"2 turkeys cost $60".match(/(?<!\$)\b\d+/g),结果是 2
    • 捕获前瞻断言和后瞻断言所匹配的内容,或者部分内容:只需将该部分包装在额外的括号中
      • 例如,"1 turkey costs 30€".match(/\d+(?=(€|kr))/),结果是 30, €
      • 例如,"1 turkey costs $30".match(/(?<=(\$|£))\d+/),结果是 30, $

# 灾难性回溯

  • 有些正则表达式看起来很简单,但执行起来耗时却非常长,甚至会导致 JavaScript 引擎挂起
  • 由于贪婪模式或惰性模式的存在,使得引擎遍历了所有排列组合,才得出答案
  • 解决方法:
    • 减少可能的组合数量:/^(\w+\s?)*$/ --- /^(\w+\s)*\w*$/
    • 禁止量词的回溯:在常规量词之后添加 +,则常规量词就变成了占有型量词
      • 例如,可以使用 \d++ 替代 \d+ 来阻止回溯
      • 占有型量词实际上比常规量词更简单,尽可能多地匹配,没有任何回溯
      • 基于前瞻变换模拟:在不回溯的情况下尽可能多地重复 \w 的模式可以写为 (?=(\w+))\1

# 粘性修饰符 y

  • y:在源字符串中的指定位置进行搜索
  • regexp.lastIndexregexp.exec(str)
  • regexp = /\w+/y

# 正则表达式和字符串的方法

  • str.match(regexp)
  • str.matchAll(regexp)
  • str.split(regexp|substr, limit)
  • str.search(regexp)
  • str.replace(str|regexp, str|func)
  • str.replaceAll(str|regexp, str|func)
  • regexp.exec(str)
  • regexp.test(str)
  • 参考 (opens new window)
Last Updated: 10/8/2022, 2:29:52 PM