【前端】JavaScript基础(正则表达式)
Dandelion 9/2/2022 JS
# 概述
- 模式和修饰符
- 字符类
- 锚点:字符串开始(^)和末尾($)标识符
- 词边界:\b
- 特殊字符和转义
- 集合和范围
- 量词:+/*/?/{n}
- 贪婪量词和惰性量词
- 捕获组
- 模式中的反向引用
- 选择(OR)|
- 前瞻断言与后瞻断言
- 灾难性回溯
- 粘性修饰符 y
- 正则表达式和字符串的方法
- 参考 (opens new window)
# 模式和修饰符
- 创建 RegExp 对象的两种语法
regexp = new RegExp("pattern");:可动态创建表达式regexp = /pattern/;:不允许插入表达式
- 修饰符(6 个)
i:搜索时不区分大小写g:搜索时会寻找所有的匹配项,否则仅返回第一个匹配项m:多行模式s:启用 dotall 模式,允许点.匹配换行符\nu:开启完整的 Unicode 支持,能够正确处理代理对y:粘滞模式,在文本中的确切位置搜索
- 搜索:
str.match(regexp)- 若正则表达式包含修饰符
g,返回一个由所有匹配项所构成的数组 - 若没有修饰符,则会以数组形式返回第一个匹配项
- 如果没有匹配项,则返回 null
- 注:若希望结果始终是一个数组,则
matches = str.match(regexp) || []
- 若正则表达式包含修饰符
- 替换:
str.replace(regexp, replacement)- 若带有修饰符
g,则替换所有匹配项,否则只替换第一个
- 若带有修饰符
- 测试:
regexp.test(str)- 至少找到一个匹配项,如果找到了,则返回 true,否则返回 false
# 字符类
- 数字类:
\d,对应于任何一位数字,即从0到9的字符 - 空格符:
\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:可以匹配gogogo或Gogo等(括号被从左到右编号)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表示gra或ey - 要将选择应用于模式中一部分内容的选择,可以将其括在括号中:
love (HTML|CSS)匹配love HTML或love CSS
- 例如,
# 前瞻断言与后瞻断言
- 前瞻断言:
X(?=Y),表示仅在后面是Y时匹配X- 当查找
X(?=Y)时,正则表达式引擎会找到X,然后检查其后是否有Y。如果没有,则跳过潜在匹配,并继续搜索。 X(?=Y)(?=Z)匹配X后跟Y和Z(模式Y和Z不互斥)- 例如,
"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.lastIndex与regexp.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)